@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,641 @@
|
|
|
1
|
+
import { x402Gate } from "../server/x402Gate.js";
|
|
2
|
+
import { SOLANA_ADDR_RE, heliusRpcUrl, rpcCall } from "./heliusDataRoutes.js";
|
|
3
|
+
import { callOpenAI } from "../utils/llm.js";
|
|
4
|
+
import { TTLCache } from "../utils/cache.js";
|
|
5
|
+
import { saveReport } from "../utils/reportStore.js";
|
|
6
|
+
// ── Signature regex (base58, 64-88 chars) ──────────────────────────────
|
|
7
|
+
const SOLANA_SIG_RE = /^[1-9A-HJ-NP-Za-km-z]{64,88}$/;
|
|
8
|
+
// ── Caches ─────────────────────────────────────────────────────────────
|
|
9
|
+
const txExplainerCache = new TTLCache(5 * 60_000); // 5 min
|
|
10
|
+
const memecoinScoreCache = new TTLCache(60_000); // 60 s
|
|
11
|
+
const walletRiskCache = new TTLCache(30_000); // 30 s
|
|
12
|
+
// ── Helper: build public URLs for a report ───────────────────────────────
|
|
13
|
+
function reportUrls(id) {
|
|
14
|
+
const base = process.env.RAILWAY_PUBLIC_DOMAIN
|
|
15
|
+
? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`
|
|
16
|
+
: process.env.SWARMX_BASE_URL ?? "https://api.swarmx.io";
|
|
17
|
+
return {
|
|
18
|
+
reportUrl: `${base}/report/${id}`,
|
|
19
|
+
badgeUrl: `${base}/badge/${id}`,
|
|
20
|
+
badgeMarkdown: `[](${base}/report/${id})`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// ── Helper: get SwarmsService or null ──────────────────────────────────
|
|
24
|
+
function getSwarmsService(runtime) {
|
|
25
|
+
const svc = runtime.getService("SWARMS");
|
|
26
|
+
return svc?.isAvailable() ? svc : null;
|
|
27
|
+
}
|
|
28
|
+
// ── JSON parse helper ──────────────────────────────────────────────────
|
|
29
|
+
function tryParseJson(raw) {
|
|
30
|
+
try {
|
|
31
|
+
const match = raw.match(/\{[\s\S]*\}/);
|
|
32
|
+
if (match) {
|
|
33
|
+
return JSON.parse(match[0]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// not valid JSON
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
// ── Swarm output extractor ─────────────────────────────────────────────
|
|
42
|
+
function extractSwarmOutput(result) {
|
|
43
|
+
const output = result.output;
|
|
44
|
+
if (typeof output === "string")
|
|
45
|
+
return output;
|
|
46
|
+
if (Array.isArray(output)) {
|
|
47
|
+
return output
|
|
48
|
+
.map((item) => {
|
|
49
|
+
if (typeof item === "string")
|
|
50
|
+
return item;
|
|
51
|
+
if (item && typeof item === "object") {
|
|
52
|
+
const obj = item;
|
|
53
|
+
const role = obj.role ?? obj.agent_name ?? "agent";
|
|
54
|
+
const content = obj.content ?? obj.text ?? obj.output ?? "";
|
|
55
|
+
return `[${role}]\n${content}`;
|
|
56
|
+
}
|
|
57
|
+
return String(item);
|
|
58
|
+
})
|
|
59
|
+
.join("\n\n");
|
|
60
|
+
}
|
|
61
|
+
if (output && typeof output === "object") {
|
|
62
|
+
const nested = output;
|
|
63
|
+
if (typeof nested.output === "string")
|
|
64
|
+
return nested.output;
|
|
65
|
+
if (typeof nested.content === "string")
|
|
66
|
+
return nested.content;
|
|
67
|
+
return JSON.stringify(output);
|
|
68
|
+
}
|
|
69
|
+
if (typeof result === "string")
|
|
70
|
+
return result;
|
|
71
|
+
return JSON.stringify(result);
|
|
72
|
+
}
|
|
73
|
+
// ── Catalog entries ─────────────────────────────────────────────────────
|
|
74
|
+
export const CRYPTO_ANALYSIS_CATALOG = [
|
|
75
|
+
{
|
|
76
|
+
name: "Transaction Explainer",
|
|
77
|
+
description: "Explain any Solana transaction in plain English — type classification, participants, tokens involved, and summary",
|
|
78
|
+
path: "/x402/tx-explainer",
|
|
79
|
+
method: "POST",
|
|
80
|
+
priceUsd: "0.03",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "Memecoin Score",
|
|
84
|
+
description: "Multi-agent memecoin risk scoring — contract authority checks, holder concentration, and SAFE/CAUTION/DANGER/SCAM verdict (SequentialWorkflow, 3 agents)",
|
|
85
|
+
path: "/x402/memecoin-score",
|
|
86
|
+
method: "POST",
|
|
87
|
+
priceUsd: "0.05",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "Wallet Risk Score",
|
|
91
|
+
description: "Multi-agent wallet risk assessment — transaction pattern analysis and risk level scoring (SequentialWorkflow, 2 agents)",
|
|
92
|
+
path: "/x402/wallet-risk-score",
|
|
93
|
+
method: "POST",
|
|
94
|
+
priceUsd: "0.05",
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
// ── Free tier placeholder ──────────────────────────────────────────────
|
|
98
|
+
const FREE_TIER_PLACEHOLDER = "[Connect wallet to see full details]";
|
|
99
|
+
// ── Routes ─────────────────────────────────────────────────────────────
|
|
100
|
+
export const cryptoAnalysisRoutes = [
|
|
101
|
+
// ── POST /x402/tx-explainer — $0.03 ──────────────────────────────────
|
|
102
|
+
{
|
|
103
|
+
type: "POST",
|
|
104
|
+
path: "/x402/tx-explainer",
|
|
105
|
+
handler: async (req, res, runtime) => {
|
|
106
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
107
|
+
amountUsd: "0.03",
|
|
108
|
+
description: "Solana transaction explainer — plain English analysis of any transaction",
|
|
109
|
+
});
|
|
110
|
+
if (!gate.paid)
|
|
111
|
+
return;
|
|
112
|
+
const body = req.body ?? {};
|
|
113
|
+
const signature = body.signature;
|
|
114
|
+
if (!signature || typeof signature !== "string") {
|
|
115
|
+
res.status(400).json({ error: "Missing required field: signature (transaction signature string)" });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!SOLANA_SIG_RE.test(signature)) {
|
|
119
|
+
res.status(400).json({ error: "Invalid transaction signature format" });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Check cache
|
|
123
|
+
const cached = txExplainerCache.get(`tx-explain:${signature}`);
|
|
124
|
+
if (cached) {
|
|
125
|
+
res.json({
|
|
126
|
+
...cached,
|
|
127
|
+
payment: {
|
|
128
|
+
amount: "0.03",
|
|
129
|
+
transaction: gate.transaction,
|
|
130
|
+
network: gate.network,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const heliusKey = runtime.getSetting("HELIUS_API_KEY");
|
|
136
|
+
const openaiKey = String(runtime.getSetting("OPENAI_API_KEY") ?? "");
|
|
137
|
+
if (!heliusKey) {
|
|
138
|
+
res.status(503).json({ error: "HELIUS_API_KEY not configured" });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const rpcUrl = heliusRpcUrl(String(heliusKey));
|
|
143
|
+
const tx = await rpcCall(rpcUrl, "getTransaction", [
|
|
144
|
+
signature,
|
|
145
|
+
{ encoding: "jsonParsed", maxSupportedTransactionVersion: 0 },
|
|
146
|
+
]);
|
|
147
|
+
if (!tx) {
|
|
148
|
+
res.status(404).json({ error: "Transaction not found" });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const systemPrompt = "You are a Solana blockchain transaction analyst. Explain transactions in plain English. " +
|
|
152
|
+
"Given a parsed transaction, produce JSON: " +
|
|
153
|
+
'{ "type": "<swap|transfer|nft-mint|nft-sale|stake|unstake|program-interaction|unknown>", ' +
|
|
154
|
+
'"explanation": "<2-4 sentences>", ' +
|
|
155
|
+
'"participants": [{"address": "...", "role": "..."}], ' +
|
|
156
|
+
'"tokensInvolved": [{"mint": "...", "amount": "...", "direction": "..."}], ' +
|
|
157
|
+
'"summary": "<1 sentence>" }. ' +
|
|
158
|
+
"Analyze ONLY data provided. Do NOT fabricate. SOL amounts in lamports (1 SOL = 1B lamports). " +
|
|
159
|
+
"Output ONLY JSON.";
|
|
160
|
+
const userPrompt = `Explain this Solana transaction:\n\n${JSON.stringify(tx, null, 2).slice(0, 8000)}`;
|
|
161
|
+
let raw;
|
|
162
|
+
if (openaiKey) {
|
|
163
|
+
raw = await callOpenAI({
|
|
164
|
+
apiKey: openaiKey,
|
|
165
|
+
model: "gpt-4o-mini",
|
|
166
|
+
systemPrompt,
|
|
167
|
+
userPrompt,
|
|
168
|
+
maxTokens: 2048,
|
|
169
|
+
temperature: 0.2,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Fallback: Swarms API
|
|
174
|
+
const swarmsService = getSwarmsService(runtime);
|
|
175
|
+
if (!swarmsService) {
|
|
176
|
+
res.status(503).json({ error: "Neither OPENAI_API_KEY nor Swarms service available" });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const result = await swarmsService.runAgent({
|
|
180
|
+
agent_name: "TxExplainer",
|
|
181
|
+
model_name: "gpt-4o-mini",
|
|
182
|
+
system_prompt: systemPrompt,
|
|
183
|
+
max_loops: 1,
|
|
184
|
+
max_tokens: 2048,
|
|
185
|
+
temperature: 0.2,
|
|
186
|
+
role: "worker",
|
|
187
|
+
}, userPrompt);
|
|
188
|
+
raw = String(result.outputs ?? result);
|
|
189
|
+
}
|
|
190
|
+
const parsed = tryParseJson(raw);
|
|
191
|
+
const explanation = parsed ?? {
|
|
192
|
+
type: "unknown",
|
|
193
|
+
explanation: raw.slice(0, 500),
|
|
194
|
+
participants: [],
|
|
195
|
+
tokensInvolved: [],
|
|
196
|
+
summary: raw.slice(0, 200),
|
|
197
|
+
};
|
|
198
|
+
// Free tier: show type + summary only
|
|
199
|
+
let responseData;
|
|
200
|
+
if (gate.amountUsd === 0) {
|
|
201
|
+
responseData = {
|
|
202
|
+
type: explanation.type ?? "unknown",
|
|
203
|
+
summary: explanation.summary ?? FREE_TIER_PLACEHOLDER,
|
|
204
|
+
_preview: true,
|
|
205
|
+
_message: `Type: ${explanation.type ?? "unknown"}. Pay $0.03 to see full explanation.`,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
responseData = explanation;
|
|
210
|
+
}
|
|
211
|
+
// Save report
|
|
212
|
+
const reportId = saveReport({
|
|
213
|
+
type: "token-risk", // closest available type
|
|
214
|
+
createdAt: new Date().toISOString(),
|
|
215
|
+
input: { mint: signature },
|
|
216
|
+
result: explanation,
|
|
217
|
+
riskScore: null,
|
|
218
|
+
paid: gate.amountUsd > 0,
|
|
219
|
+
});
|
|
220
|
+
const urls = reportUrls(reportId);
|
|
221
|
+
const cacheData = {
|
|
222
|
+
...responseData,
|
|
223
|
+
...urls,
|
|
224
|
+
template: "TxExplainer",
|
|
225
|
+
freeRemaining: gate.freeRemaining,
|
|
226
|
+
};
|
|
227
|
+
txExplainerCache.set(`tx-explain:${signature}`, cacheData);
|
|
228
|
+
res.json({
|
|
229
|
+
...cacheData,
|
|
230
|
+
payment: {
|
|
231
|
+
amount: "0.03",
|
|
232
|
+
transaction: gate.transaction,
|
|
233
|
+
network: gate.network,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/tx-explainer] Execution failed");
|
|
239
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
// ── POST /x402/memecoin-score — $0.05 ────────────────────────────────
|
|
244
|
+
{
|
|
245
|
+
type: "POST",
|
|
246
|
+
path: "/x402/memecoin-score",
|
|
247
|
+
handler: async (req, res, runtime) => {
|
|
248
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
249
|
+
amountUsd: "0.05",
|
|
250
|
+
description: "Multi-agent memecoin risk scoring (3 agents, SequentialWorkflow)",
|
|
251
|
+
});
|
|
252
|
+
if (!gate.paid)
|
|
253
|
+
return;
|
|
254
|
+
const body = req.body ?? {};
|
|
255
|
+
const mint = body.mint;
|
|
256
|
+
if (!mint || typeof mint !== "string") {
|
|
257
|
+
res.status(400).json({ error: "Missing required field: mint (SPL token mint address)" });
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (!SOLANA_ADDR_RE.test(mint)) {
|
|
261
|
+
res.status(400).json({ error: "Invalid mint address format" });
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
// Check cache
|
|
265
|
+
const cached = memecoinScoreCache.get(`memecoin:${mint}`);
|
|
266
|
+
if (cached) {
|
|
267
|
+
res.json({
|
|
268
|
+
...cached,
|
|
269
|
+
payment: {
|
|
270
|
+
amount: "0.05",
|
|
271
|
+
transaction: gate.transaction,
|
|
272
|
+
network: gate.network,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const swarmsService = getSwarmsService(runtime);
|
|
278
|
+
if (!swarmsService) {
|
|
279
|
+
res.status(503).json({ error: "Swarms service unavailable" });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// ── Pre-fetch on-chain data via Helius (non-fatal) ────────────────
|
|
283
|
+
let onChainContext = "";
|
|
284
|
+
const heliusKey = String(runtime.getSetting("HELIUS_API_KEY") ?? "");
|
|
285
|
+
if (heliusKey) {
|
|
286
|
+
try {
|
|
287
|
+
const rpcUrl = heliusRpcUrl(heliusKey);
|
|
288
|
+
const [accountInfo, largestAccounts, tokenSupply] = await Promise.all([
|
|
289
|
+
rpcCall(rpcUrl, "getAccountInfo", [mint, { encoding: "jsonParsed" }]).catch(() => null),
|
|
290
|
+
rpcCall(rpcUrl, "getTokenLargestAccounts", [mint]).catch(() => null),
|
|
291
|
+
rpcCall(rpcUrl, "getTokenSupply", [mint]).catch(() => null),
|
|
292
|
+
]);
|
|
293
|
+
const parts = [];
|
|
294
|
+
if (accountInfo) {
|
|
295
|
+
parts.push(`Account info:\n${JSON.stringify(accountInfo, null, 2).slice(0, 2000)}`);
|
|
296
|
+
}
|
|
297
|
+
if (largestAccounts?.value) {
|
|
298
|
+
parts.push(`Top holders:\n${JSON.stringify(largestAccounts.value.slice(0, 10), null, 2)}`);
|
|
299
|
+
}
|
|
300
|
+
if (tokenSupply?.value) {
|
|
301
|
+
parts.push(`Token supply:\n${JSON.stringify(tokenSupply.value, null, 2)}`);
|
|
302
|
+
}
|
|
303
|
+
if (parts.length > 0) {
|
|
304
|
+
onChainContext = "\n\n--- ON-CHAIN DATA ---\n" + parts.join("\n\n") + "\n--- END ON-CHAIN DATA ---\n";
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
runtime.logger.warn({ error: err instanceof Error ? err.message : String(err) }, "[x402/memecoin-score] Helius lookup failed, proceeding with LLM knowledge only");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
const result = await swarmsService.runSwarm({
|
|
313
|
+
name: `memecoin-score-${Date.now()}`,
|
|
314
|
+
description: `Memecoin risk scoring: ${mint}`,
|
|
315
|
+
agents: [
|
|
316
|
+
{
|
|
317
|
+
agent_name: "ContractScanner",
|
|
318
|
+
system_prompt: "You are a Solana smart contract scanner specializing in memecoin risk detection.\n\n" +
|
|
319
|
+
"Analyze the token's contract properties:\n" +
|
|
320
|
+
"- Mint authority: can new tokens be minted? (active/renounced)\n" +
|
|
321
|
+
"- Freeze authority: can transfers be frozen? (active/renounced)\n" +
|
|
322
|
+
"- Mutability: can token metadata be changed?\n" +
|
|
323
|
+
"- Honeypot patterns: can tokens actually be sold?\n" +
|
|
324
|
+
"- Hidden fee mechanisms\n" +
|
|
325
|
+
"- Admin backdoors without timelocks\n\n" +
|
|
326
|
+
"Output ONLY a JSON object:\n" +
|
|
327
|
+
"{\n" +
|
|
328
|
+
' "contractRiskScore": <0-100>,\n' +
|
|
329
|
+
' "mintAuthority": "active|renounced|unknown",\n' +
|
|
330
|
+
' "freezeAuthority": "active|renounced|unknown",\n' +
|
|
331
|
+
' "mutable": true|false,\n' +
|
|
332
|
+
' "findings": [{"severity": "...", "title": "...", "description": "..."}]\n' +
|
|
333
|
+
"}\n" +
|
|
334
|
+
"Output ONLY JSON — no markdown fences.",
|
|
335
|
+
model_name: "gpt-4o",
|
|
336
|
+
role: "worker",
|
|
337
|
+
max_loops: 1,
|
|
338
|
+
max_tokens: 4096,
|
|
339
|
+
temperature: 0.2,
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
agent_name: "TokenomicsAnalyst",
|
|
343
|
+
system_prompt: "You are a tokenomics analyst specializing in memecoin holder distribution analysis.\n\n" +
|
|
344
|
+
"Analyze holder concentration and distribution:\n" +
|
|
345
|
+
"- Top holder percentage (whale risk)\n" +
|
|
346
|
+
"- Developer/team wallet holdings\n" +
|
|
347
|
+
"- Liquidity pool token distribution\n" +
|
|
348
|
+
"- Insider allocation patterns\n" +
|
|
349
|
+
"- Wash trading indicators\n" +
|
|
350
|
+
"- Supply distribution fairness\n\n" +
|
|
351
|
+
"Output ONLY a JSON object:\n" +
|
|
352
|
+
"{\n" +
|
|
353
|
+
' "tokenomicsRiskScore": <0-100>,\n' +
|
|
354
|
+
' "topHolderPct": "<percentage or unknown>",\n' +
|
|
355
|
+
' "liquidityLocked": "yes|no|unknown",\n' +
|
|
356
|
+
' "findings": [{"severity": "...", "title": "...", "description": "..."}]\n' +
|
|
357
|
+
"}\n" +
|
|
358
|
+
"Output ONLY JSON — no markdown fences.",
|
|
359
|
+
model_name: "gpt-4o",
|
|
360
|
+
role: "worker",
|
|
361
|
+
max_loops: 1,
|
|
362
|
+
max_tokens: 4096,
|
|
363
|
+
temperature: 0.3,
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
agent_name: "RiskSynthesizer",
|
|
367
|
+
system_prompt: "You are a memecoin risk judge. Combine ContractScanner and TokenomicsAnalyst findings into a final score.\n\n" +
|
|
368
|
+
"Output ONLY a JSON object:\n" +
|
|
369
|
+
"{\n" +
|
|
370
|
+
' "score": <0-100>,\n' +
|
|
371
|
+
' "verdict": "SAFE"|"CAUTION"|"DANGER"|"SCAM",\n' +
|
|
372
|
+
' "contract": { "mintAuthority": "...", "freezeAuthority": "...", "riskScore": <0-100> },\n' +
|
|
373
|
+
' "tokenomics": { "topHolderPct": "...", "riskScore": <0-100> },\n' +
|
|
374
|
+
' "redFlags": ["..."],\n' +
|
|
375
|
+
' "summary": "<1-2 sentence non-technical summary>"\n' +
|
|
376
|
+
"}\n\n" +
|
|
377
|
+
"Scoring: 0-25 SAFE, 26-50 CAUTION, 51-75 DANGER, 76-100 SCAM.\n" +
|
|
378
|
+
"Only report REAL issues. Output ONLY JSON — no markdown fences.",
|
|
379
|
+
model_name: "gpt-4o",
|
|
380
|
+
role: "worker",
|
|
381
|
+
max_loops: 1,
|
|
382
|
+
max_tokens: 4096,
|
|
383
|
+
temperature: 0.2,
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
swarm_type: "SequentialWorkflow",
|
|
387
|
+
task: `Assess the risk of the following Solana memecoin.\nToken mint: ${mint}\n` +
|
|
388
|
+
"Analyze contract authorities, holder concentration, and produce a final risk verdict." +
|
|
389
|
+
onChainContext,
|
|
390
|
+
max_loops: 1,
|
|
391
|
+
rules: "ContractScanner checks authorities first, TokenomicsAnalyst evaluates distribution, then RiskSynthesizer combines both into a final score and verdict.",
|
|
392
|
+
});
|
|
393
|
+
const rawOutput = extractSwarmOutput(result);
|
|
394
|
+
const parsed = tryParseJson(rawOutput);
|
|
395
|
+
// Normalize the result
|
|
396
|
+
const score = typeof parsed?.score === "number" ? parsed.score : 50;
|
|
397
|
+
const verdict = typeof parsed?.verdict === "string" ? parsed.verdict : "CAUTION";
|
|
398
|
+
const contract = parsed?.contract ?? {};
|
|
399
|
+
const tokenomics = parsed?.tokenomics ?? {};
|
|
400
|
+
const redFlags = Array.isArray(parsed?.redFlags) ? parsed.redFlags : [];
|
|
401
|
+
const summary = typeof parsed?.summary === "string" ? parsed.summary : rawOutput.slice(0, 500);
|
|
402
|
+
const fullResult = { score, verdict, contract, tokenomics, redFlags, summary };
|
|
403
|
+
// Free tier: show score + verdict + redFlag count only
|
|
404
|
+
let responseData;
|
|
405
|
+
if (gate.amountUsd === 0) {
|
|
406
|
+
responseData = {
|
|
407
|
+
score,
|
|
408
|
+
verdict,
|
|
409
|
+
redFlagCount: redFlags.length,
|
|
410
|
+
_preview: true,
|
|
411
|
+
_message: `Verdict: ${verdict} (${score}/100). ${redFlags.length} red flags found. Pay $0.05 to see full details.`,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
responseData = fullResult;
|
|
416
|
+
}
|
|
417
|
+
// Save report
|
|
418
|
+
const reportId = saveReport({
|
|
419
|
+
type: "token-risk",
|
|
420
|
+
createdAt: new Date().toISOString(),
|
|
421
|
+
input: { mint },
|
|
422
|
+
result: fullResult,
|
|
423
|
+
riskScore: score,
|
|
424
|
+
paid: gate.amountUsd > 0,
|
|
425
|
+
});
|
|
426
|
+
const urls = reportUrls(reportId);
|
|
427
|
+
const cacheData = {
|
|
428
|
+
...responseData,
|
|
429
|
+
...urls,
|
|
430
|
+
template: "MemecoinScore",
|
|
431
|
+
freeRemaining: gate.freeRemaining,
|
|
432
|
+
};
|
|
433
|
+
memecoinScoreCache.set(`memecoin:${mint}`, cacheData);
|
|
434
|
+
res.json({
|
|
435
|
+
...cacheData,
|
|
436
|
+
payment: {
|
|
437
|
+
amount: "0.05",
|
|
438
|
+
transaction: gate.transaction,
|
|
439
|
+
network: gate.network,
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
catch (err) {
|
|
444
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/memecoin-score] Swarm execution failed");
|
|
445
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
// ── POST /x402/wallet-risk-score — $0.05 ─────────────────────────────
|
|
450
|
+
{
|
|
451
|
+
type: "POST",
|
|
452
|
+
path: "/x402/wallet-risk-score",
|
|
453
|
+
handler: async (req, res, runtime) => {
|
|
454
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
455
|
+
amountUsd: "0.05",
|
|
456
|
+
description: "Multi-agent wallet risk assessment (2 agents, SequentialWorkflow)",
|
|
457
|
+
});
|
|
458
|
+
if (!gate.paid)
|
|
459
|
+
return;
|
|
460
|
+
const body = req.body ?? {};
|
|
461
|
+
const address = body.address;
|
|
462
|
+
if (!address || typeof address !== "string") {
|
|
463
|
+
res.status(400).json({ error: "Missing required field: address (Solana wallet address)" });
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (!SOLANA_ADDR_RE.test(address)) {
|
|
467
|
+
res.status(400).json({ error: "Invalid Solana address format" });
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
// Check cache
|
|
471
|
+
const cached = walletRiskCache.get(`wallet-risk:${address}`);
|
|
472
|
+
if (cached) {
|
|
473
|
+
res.json({
|
|
474
|
+
...cached,
|
|
475
|
+
payment: {
|
|
476
|
+
amount: "0.05",
|
|
477
|
+
transaction: gate.transaction,
|
|
478
|
+
network: gate.network,
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
// HARD REQUIREMENT: Helius must be available
|
|
484
|
+
const heliusKey = runtime.getSetting("HELIUS_API_KEY");
|
|
485
|
+
if (!heliusKey) {
|
|
486
|
+
res.status(503).json({ error: "HELIUS_API_KEY not configured" });
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const swarmsService = getSwarmsService(runtime);
|
|
490
|
+
if (!swarmsService) {
|
|
491
|
+
res.status(503).json({ error: "Swarms service unavailable" });
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
// ── Pre-fetch on-chain data ───────────────────────────────────────
|
|
495
|
+
let onChainContext = "";
|
|
496
|
+
try {
|
|
497
|
+
const rpcUrl = heliusRpcUrl(String(heliusKey));
|
|
498
|
+
const [balance, signatures] = await Promise.all([
|
|
499
|
+
rpcCall(rpcUrl, "getBalance", [address]),
|
|
500
|
+
rpcCall(rpcUrl, "getSignaturesForAddress", [address, { limit: 20 }]),
|
|
501
|
+
]);
|
|
502
|
+
const parts = [];
|
|
503
|
+
parts.push(`Balance: ${JSON.stringify(balance)}`);
|
|
504
|
+
const sigs = signatures ?? [];
|
|
505
|
+
parts.push(`Recent signatures (${sigs.length}):\n${JSON.stringify(sigs.slice(0, 5), null, 2)}`);
|
|
506
|
+
// Fetch top 5 transactions
|
|
507
|
+
const topSigs = sigs.slice(0, 5);
|
|
508
|
+
const txDetails = await Promise.all(topSigs.map(async (sig, idx) => {
|
|
509
|
+
try {
|
|
510
|
+
return await rpcCall(rpcUrl, "getTransaction", [sig.signature, { encoding: "jsonParsed", maxSupportedTransactionVersion: 0 }], 10 + idx);
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
}));
|
|
516
|
+
const validTxs = txDetails.filter(Boolean);
|
|
517
|
+
if (validTxs.length > 0) {
|
|
518
|
+
parts.push(`Transaction details (${validTxs.length}):\n${JSON.stringify(validTxs, null, 2).slice(0, 6000)}`);
|
|
519
|
+
}
|
|
520
|
+
onChainContext = "\n\n--- ON-CHAIN DATA ---\n" + parts.join("\n\n") + "\n--- END ON-CHAIN DATA ---\n";
|
|
521
|
+
}
|
|
522
|
+
catch (err) {
|
|
523
|
+
// HARD REQUIREMENT: If Helius unavailable, return 503
|
|
524
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/wallet-risk-score] Helius RPC failed");
|
|
525
|
+
res.status(503).json({ error: "Helius RPC unavailable" });
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const result = await swarmsService.runSwarm({
|
|
530
|
+
name: `wallet-risk-${Date.now()}`,
|
|
531
|
+
description: `Wallet risk assessment: ${address}`,
|
|
532
|
+
agents: [
|
|
533
|
+
{
|
|
534
|
+
agent_name: "TransactionAnalyzer",
|
|
535
|
+
system_prompt: "You are a blockchain forensic analyst specializing in Solana wallet activity pattern detection.\n\n" +
|
|
536
|
+
"Categorize the wallet's activity patterns:\n" +
|
|
537
|
+
"- Transaction frequency and timing patterns\n" +
|
|
538
|
+
"- Interaction with known scam/exploit contracts\n" +
|
|
539
|
+
"- Wash trading indicators\n" +
|
|
540
|
+
"- Sybil attack patterns (many small wallets funding one)\n" +
|
|
541
|
+
"- MEV bot behavior (sandwich attacks, frontrunning)\n" +
|
|
542
|
+
"- Mixer/tumbler usage\n" +
|
|
543
|
+
"- Rapid token cycling (pump and dump participation)\n" +
|
|
544
|
+
"- Airdrop farming patterns\n\n" +
|
|
545
|
+
"Output ONLY a JSON object:\n" +
|
|
546
|
+
"{\n" +
|
|
547
|
+
' "patternRiskScore": <0-100>,\n' +
|
|
548
|
+
' "patterns": [{"type": "...", "description": "...", "riskLevel": "low|moderate|elevated|high|critical"}],\n' +
|
|
549
|
+
' "txCount": <number>,\n' +
|
|
550
|
+
' "dominantActivity": "<description>"\n' +
|
|
551
|
+
"}\n" +
|
|
552
|
+
"Output ONLY JSON — no markdown fences.",
|
|
553
|
+
model_name: "gpt-4o",
|
|
554
|
+
role: "worker",
|
|
555
|
+
max_loops: 1,
|
|
556
|
+
max_tokens: 4096,
|
|
557
|
+
temperature: 0.2,
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
agent_name: "RiskScorer",
|
|
561
|
+
system_prompt: "You are a crypto compliance risk scorer. Combine TransactionAnalyzer findings into a final risk assessment.\n\n" +
|
|
562
|
+
"Output ONLY a JSON object:\n" +
|
|
563
|
+
"{\n" +
|
|
564
|
+
' "riskScore": <0-100>,\n' +
|
|
565
|
+
' "riskLevel": "low"|"moderate"|"elevated"|"high"|"critical",\n' +
|
|
566
|
+
' "patterns": [{"type": "...", "description": "...", "riskLevel": "..."}],\n' +
|
|
567
|
+
' "flags": ["..."],\n' +
|
|
568
|
+
' "summary": "<1-2 sentence non-technical summary>"\n' +
|
|
569
|
+
"}\n\n" +
|
|
570
|
+
"Risk scoring: 0-20 low, 21-40 moderate, 41-60 elevated, 61-80 high, 81-100 critical.\n" +
|
|
571
|
+
"Only report REAL patterns found in the data. Output ONLY JSON — no markdown fences.",
|
|
572
|
+
model_name: "gpt-4o",
|
|
573
|
+
role: "worker",
|
|
574
|
+
max_loops: 1,
|
|
575
|
+
max_tokens: 4096,
|
|
576
|
+
temperature: 0.2,
|
|
577
|
+
},
|
|
578
|
+
],
|
|
579
|
+
swarm_type: "SequentialWorkflow",
|
|
580
|
+
task: `Assess the risk profile of the following Solana wallet.\nWallet address: ${address}\n` +
|
|
581
|
+
"Analyze transaction patterns and produce a final risk score." +
|
|
582
|
+
onChainContext,
|
|
583
|
+
max_loops: 1,
|
|
584
|
+
rules: "TransactionAnalyzer categorizes activity patterns first, then RiskScorer produces the final risk assessment.",
|
|
585
|
+
});
|
|
586
|
+
const rawOutput = extractSwarmOutput(result);
|
|
587
|
+
const parsed = tryParseJson(rawOutput);
|
|
588
|
+
// Normalize the result
|
|
589
|
+
const riskScore = typeof parsed?.riskScore === "number" ? parsed.riskScore : 50;
|
|
590
|
+
const riskLevel = typeof parsed?.riskLevel === "string" ? parsed.riskLevel : "moderate";
|
|
591
|
+
const patterns = Array.isArray(parsed?.patterns) ? parsed.patterns : [];
|
|
592
|
+
const flags = Array.isArray(parsed?.flags) ? parsed.flags : [];
|
|
593
|
+
const summary = typeof parsed?.summary === "string" ? parsed.summary : rawOutput.slice(0, 500);
|
|
594
|
+
const fullResult = { riskScore, riskLevel, patterns, flags, summary };
|
|
595
|
+
// Free tier: show riskScore + riskLevel only
|
|
596
|
+
let responseData;
|
|
597
|
+
if (gate.amountUsd === 0) {
|
|
598
|
+
responseData = {
|
|
599
|
+
riskScore,
|
|
600
|
+
riskLevel,
|
|
601
|
+
_preview: true,
|
|
602
|
+
_message: `Risk: ${riskLevel} (${riskScore}/100). Pay $0.05 to see full details.`,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
responseData = fullResult;
|
|
607
|
+
}
|
|
608
|
+
// Save report
|
|
609
|
+
const reportId = saveReport({
|
|
610
|
+
type: "token-risk",
|
|
611
|
+
createdAt: new Date().toISOString(),
|
|
612
|
+
input: { mint: address },
|
|
613
|
+
result: fullResult,
|
|
614
|
+
riskScore,
|
|
615
|
+
paid: gate.amountUsd > 0,
|
|
616
|
+
});
|
|
617
|
+
const urls = reportUrls(reportId);
|
|
618
|
+
const cacheData = {
|
|
619
|
+
...responseData,
|
|
620
|
+
...urls,
|
|
621
|
+
template: "WalletRiskScore",
|
|
622
|
+
freeRemaining: gate.freeRemaining,
|
|
623
|
+
};
|
|
624
|
+
walletRiskCache.set(`wallet-risk:${address}`, cacheData);
|
|
625
|
+
res.json({
|
|
626
|
+
...cacheData,
|
|
627
|
+
payment: {
|
|
628
|
+
amount: "0.05",
|
|
629
|
+
transaction: gate.transaction,
|
|
630
|
+
network: gate.network,
|
|
631
|
+
},
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
catch (err) {
|
|
635
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/wallet-risk-score] Swarm execution failed");
|
|
636
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
];
|
|
641
|
+
//# sourceMappingURL=cryptoAnalysisRoutes.js.map
|