solana-traderclaw 1.0.82 → 1.0.84
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/README.md +0 -1
- package/config/gateway-v1-upgraded.json5 +1 -1
- package/dist/{chunk-C24QA3MQ.js → chunk-FBS5FGW2.js} +8 -2
- package/dist/index.js +111 -44
- package/dist/src/intelligence-lab.js +1 -1
- package/package.json +1 -1
- package/skills/solana-trader/HEARTBEAT.md +21 -5
- package/skills/solana-trader/SKILL.md +11 -0
- package/skills/solana-trader/bitquery-schema.md +233 -31
- package/skills/solana-trader/refs/memory-tags.md +11 -0
- package/skills/solana-trader/workspace/TOOLS.md +0 -1
package/README.md
CHANGED
|
@@ -348,7 +348,6 @@ Pay-as-you-go or Basic tier is required for the read-only social intel tools.
|
|
|
348
348
|
### Safety
|
|
349
349
|
| Tool | Description |
|
|
350
350
|
|------|-------------|
|
|
351
|
-
| `solana_killswitch` | Toggle emergency kill switch |
|
|
352
351
|
| `solana_killswitch_status` | Check kill switch state |
|
|
353
352
|
|
|
354
353
|
### Wallet
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
agentId: "main",
|
|
30
30
|
sessionTarget: "isolated",
|
|
31
31
|
delivery: { mode: "none" },
|
|
32
|
-
message: "CRON_JOB: strategy_evolution\n\nStep 1: Call solana_journal_summary to get aggregate performance stats (win rate, avg PnL, trade count). If fewer than
|
|
32
|
+
message: "CRON_JOB: strategy_evolution\n\nStep 1: Call solana_journal_summary to get aggregate performance stats (win rate, avg PnL, trade count). If fewer than 10 closed trades since the last strategy evolution, skip weight updates but still run pattern detection.\n\nStep 2: Call solana_memory_search for 'strategy_evolution' — find last 3 evolution cycle results. Call solana_memory_search for 'strategy_drift_warning' — find drift warnings since last evolution. Call solana_memory_search for 'pre_trade_rationale' — recent decision patterns.\n\nStep 3: Run Recurring Pattern Detection — search for learning_entry tags, group by area, check linked chains (3+ = confirmed pattern), investigate drift warnings.\n\nStep 4: Call solana_strategy_state to read current feature weights.\n\nStep 5: Call solana_trades to get recent closed trades. Apply ADL checks (direction consistency, weight velocity, reversion check).\n\nStep 6: Compute proposed weight changes. Score each with VFM (Frequency + Failure Reduction + Self-Cost). Only apply changes scoring >= 3/5.\n\nStep 7: Verify guardrails: maxDeltaOk, sumWeightsOk, minTradesOk, floorCapOk. If all pass, call solana_strategy_update with incremented version.\n\nStep 8: Run Named Pattern Recognition — search for winning trade clusters, catalog new patterns, evolve existing ones.\n\nStep 9: Evaluate discovery filter performance. Log all results via solana_memory_write with tags: strategy_evolution, vfm_scorecard, pattern_detection, named_pattern.\n\nFORMATTING RULES:\n- Every token reference MUST use SYMBOL (full_CA) format.\n- Do not execute trades. Do not ask questions.",
|
|
33
33
|
enabled: true
|
|
34
34
|
},
|
|
35
35
|
{
|
|
@@ -53,8 +53,14 @@ var IntelligenceLab = class {
|
|
|
53
53
|
} else {
|
|
54
54
|
candidates.push(full);
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
const MAX_CANDIDATES = 200;
|
|
57
|
+
if (candidates.length > MAX_CANDIDATES) {
|
|
58
|
+
const labeled = candidates.filter((c) => c.outcome);
|
|
59
|
+
const unlabeled = candidates.filter((c) => !c.outcome);
|
|
60
|
+
const keepUnlabeled = Math.max(0, MAX_CANDIDATES - labeled.length);
|
|
61
|
+
const trimmed = [...labeled, ...unlabeled.slice(-keepUnlabeled)];
|
|
62
|
+
candidates.length = 0;
|
|
63
|
+
candidates.push(...trimmed);
|
|
58
64
|
}
|
|
59
65
|
writeJsonFile(this.candidatesFile(), candidates);
|
|
60
66
|
return existing >= 0 ? candidates[existing] : full;
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from "./chunk-PIZZXNMQ.js";
|
|
22
22
|
import {
|
|
23
23
|
IntelligenceLab
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-FBS5FGW2.js";
|
|
25
25
|
import {
|
|
26
26
|
scrubUntrustedText
|
|
27
27
|
} from "./chunk-AI6MTHUN.js";
|
|
@@ -1142,6 +1142,33 @@ var solanaTraderPlugin = {
|
|
|
1142
1142
|
fs.writeFileSync(filePath, entries.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf-8");
|
|
1143
1143
|
return entries.length;
|
|
1144
1144
|
};
|
|
1145
|
+
const volatileStateKeys = /* @__PURE__ */ new Set(["lastHeartbeat", "lastHeartbeatAt"]);
|
|
1146
|
+
const renderSummaryBullets = (key, val) => {
|
|
1147
|
+
if (key === "lastCycleSummary" && val && typeof val === "object") {
|
|
1148
|
+
const s = val;
|
|
1149
|
+
const bullets = ["## Last Cycle Summary", ""];
|
|
1150
|
+
if (s.capitalSol !== void 0) bullets.push(`- **Capital SOL:** ${s.capitalSol}`);
|
|
1151
|
+
if (s.openPositions !== void 0) bullets.push(`- **Open Positions:** ${s.openPositions}`);
|
|
1152
|
+
if (s.signalsProcessed !== void 0) bullets.push(`- **Signals Processed:** ${s.signalsProcessed}`);
|
|
1153
|
+
if (s.topWatch) bullets.push(`- **Top Watch:** ${s.topWatch}`);
|
|
1154
|
+
if (s.xApiStatus) bullets.push(`- **X API Status:** ${s.xApiStatus}`);
|
|
1155
|
+
if (s.bitqueryStatus) bullets.push(`- **Bitquery Status:** ${s.bitqueryStatus}`);
|
|
1156
|
+
const rendered = Object.keys(s).filter((k) => !["capitalSol", "openPositions", "signalsProcessed", "topWatch", "xApiStatus", "bitqueryStatus"].includes(k));
|
|
1157
|
+
for (const rk of rendered.slice(0, 5)) {
|
|
1158
|
+
const rv = s[rk];
|
|
1159
|
+
const disp = typeof rv === "object" ? JSON.stringify(rv) : String(rv);
|
|
1160
|
+
bullets.push(`- **${rk}:** ${disp.length > 120 ? disp.slice(0, 120) + "\u2026" : disp}`);
|
|
1161
|
+
}
|
|
1162
|
+
bullets.push("");
|
|
1163
|
+
return bullets;
|
|
1164
|
+
}
|
|
1165
|
+
return [];
|
|
1166
|
+
};
|
|
1167
|
+
const formatStateValue = (val) => {
|
|
1168
|
+
if (val === null || val === void 0) return String(val);
|
|
1169
|
+
if (typeof val === "object") return JSON.stringify(val);
|
|
1170
|
+
return String(val);
|
|
1171
|
+
};
|
|
1145
1172
|
const generateMemoryMd = (aid, stateObj) => {
|
|
1146
1173
|
const lines = [
|
|
1147
1174
|
`# ${aid} \u2014 Durable Memory`,
|
|
@@ -1160,7 +1187,7 @@ var solanaTraderPlugin = {
|
|
|
1160
1187
|
if (state.walletId) identity.push(`- **Wallet:** ${state.walletId}`);
|
|
1161
1188
|
if (state.mode) identity.push(`- **Mode:** ${state.mode}`);
|
|
1162
1189
|
if (state.strategyVersion) identity.push(`- **Strategy Version:** ${state.strategyVersion}`);
|
|
1163
|
-
if (state.regime) identity.push(`- **Regime:** ${state.regime}`);
|
|
1190
|
+
if (state.regime) identity.push(`- **Regime:** ${formatStateValue(state.regime)}`);
|
|
1164
1191
|
if (state.maxPositions) identity.push(`- **Max Positions:** ${state.maxPositions}`);
|
|
1165
1192
|
if (state.maxPositionSizeSol) identity.push(`- **Max Position Size:** ${state.maxPositionSizeSol} SOL`);
|
|
1166
1193
|
if (identity.length > 0) {
|
|
@@ -1192,13 +1219,24 @@ var solanaTraderPlugin = {
|
|
|
1192
1219
|
const rc = state.regimeCanary;
|
|
1193
1220
|
lines.push("## Regime Canary", "", `- **Regime:** ${rc.regime || "unknown"}`, `- **Detected At:** ${rc.detectedAt || "unknown"}`, "");
|
|
1194
1221
|
}
|
|
1195
|
-
const
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1222
|
+
const structuredKeys = /* @__PURE__ */ new Set(["tier", "walletId", "mode", "strategyVersion", "regime", "maxPositions", "maxPositionSizeSol", "defenseMode", "killSwitchActive", "watchlist", "permanentLearnings", "regimeCanary"]);
|
|
1223
|
+
const summaryKeys = /* @__PURE__ */ new Set(["lastCycleSummary"]);
|
|
1224
|
+
const otherKeys = Object.keys(state).filter((k) => !structuredKeys.has(k) && !volatileStateKeys.has(k));
|
|
1225
|
+
const summaryRendered = [];
|
|
1226
|
+
const remainingKeys = [];
|
|
1227
|
+
for (const key of otherKeys) {
|
|
1228
|
+
if (summaryKeys.has(key)) {
|
|
1229
|
+
summaryRendered.push(...renderSummaryBullets(key, state[key]));
|
|
1230
|
+
} else {
|
|
1231
|
+
remainingKeys.push(key);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if (summaryRendered.length > 0) lines.push(...summaryRendered);
|
|
1235
|
+
if (remainingKeys.length > 0) {
|
|
1198
1236
|
lines.push("## Other State Keys", "");
|
|
1199
|
-
for (const key of
|
|
1237
|
+
for (const key of remainingKeys.slice(0, 30)) {
|
|
1200
1238
|
const val = state[key];
|
|
1201
|
-
const display =
|
|
1239
|
+
const display = formatStateValue(val);
|
|
1202
1240
|
lines.push(`- **${key}:** ${display.length > 200 ? display.slice(0, 200) + "\u2026" : display}`);
|
|
1203
1241
|
}
|
|
1204
1242
|
lines.push("");
|
|
@@ -1721,15 +1759,6 @@ ${notes}
|
|
|
1721
1759
|
})
|
|
1722
1760
|
)
|
|
1723
1761
|
});
|
|
1724
|
-
api.registerTool({
|
|
1725
|
-
name: "solana_killswitch_status",
|
|
1726
|
-
description: "Check the current kill switch state \u2014 whether it's enabled and in what mode.",
|
|
1727
|
-
parameters: Type.Object({}),
|
|
1728
|
-
execute: wrapExecute(
|
|
1729
|
-
"solana_killswitch_status",
|
|
1730
|
-
async () => get(`/api/killswitch/status?walletId=${walletId}`)
|
|
1731
|
-
)
|
|
1732
|
-
});
|
|
1733
1762
|
api.registerTool({
|
|
1734
1763
|
name: "solana_capital_status",
|
|
1735
1764
|
description: "Get your current capital status \u2014 SOL balance, open position count, unrealized/realized PnL, daily notional used, daily loss, and effective limits. **PnL:** for Solana wallets, `totalUnrealizedPnl` / `totalRealizedPnl` / `totalPnl` are returned in SOL-native units.",
|
|
@@ -1954,34 +1983,6 @@ ${notes}
|
|
|
1954
1983
|
})
|
|
1955
1984
|
)
|
|
1956
1985
|
});
|
|
1957
|
-
api.registerTool({
|
|
1958
|
-
name: "solana_wallet_token_balance",
|
|
1959
|
-
description: "Get the on-chain SPL token balance (uiAmount \u2014 source of truth) for a specific mint in your trading wallet. Returns the token amount, decimals, and USD value estimate. Use to verify actual holdings when position balances seem inconsistent.",
|
|
1960
|
-
parameters: Type.Object({
|
|
1961
|
-
tokenAddress: Type.String({ description: "Solana token mint address to check balance for" })
|
|
1962
|
-
}),
|
|
1963
|
-
execute: wrapExecute(
|
|
1964
|
-
"solana_wallet_token_balance",
|
|
1965
|
-
async (_id, params) => post("/api/wallet/token-balance", { tokenAddress: params.tokenAddress })
|
|
1966
|
-
)
|
|
1967
|
-
});
|
|
1968
|
-
api.registerTool({
|
|
1969
|
-
name: "solana_sweep_dead_tokens",
|
|
1970
|
-
description: "Sell 100% of open positions where unrealizedReturnPct \u2264 -maxLossPct to cut losses and reclaim SOL. NOT a dust/rent sweeper \u2014 this sells actual positions that are down beyond recovery. Use in dead_money_sweep cron or manual loss-cutting.",
|
|
1971
|
-
parameters: Type.Object({
|
|
1972
|
-
maxLossPct: Type.Optional(Type.Number({ description: "Maximum loss percentage threshold \u2014 positions down more than this % are sold (default: 80)" })),
|
|
1973
|
-
slippageBps: Type.Optional(Type.Number({ description: "Slippage in basis points for the sell orders (default: server default)" })),
|
|
1974
|
-
dryRun: Type.Optional(Type.Boolean({ description: "If true, return positions that would be sold without executing. Default: false" }))
|
|
1975
|
-
}),
|
|
1976
|
-
execute: wrapExecute(
|
|
1977
|
-
"solana_sweep_dead_tokens",
|
|
1978
|
-
async (_id, params) => post("/api/wallet/sweep-dead-tokens", {
|
|
1979
|
-
maxLossPct: params.maxLossPct,
|
|
1980
|
-
slippageBps: params.slippageBps,
|
|
1981
|
-
dryRun: params.dryRun
|
|
1982
|
-
})
|
|
1983
|
-
)
|
|
1984
|
-
});
|
|
1985
1986
|
api.registerTool({
|
|
1986
1987
|
name: "solana_trades",
|
|
1987
1988
|
description: "List your trade history with pagination. Returns executed trades with details like token, side, size, PnL, and timestamp.",
|
|
@@ -2449,6 +2450,29 @@ ${notes}
|
|
|
2449
2450
|
const capitalOnly = failed === 1 && steps.find((s) => !s.ok)?.step === "solana_capital_status";
|
|
2450
2451
|
startupGateState = { ok: allOk, ts: Date.now(), steps };
|
|
2451
2452
|
const k = config.apiKey && String(config.apiKey).trim() || null;
|
|
2453
|
+
if (allOk || capitalOnly) {
|
|
2454
|
+
try {
|
|
2455
|
+
const effectiveAgentId = config.agentId || "main";
|
|
2456
|
+
const stateFilePath = path.join(stateDir, `${effectiveAgentId}.json`);
|
|
2457
|
+
const existing = readJsonFile(stateFilePath);
|
|
2458
|
+
const existingState = existing?.state && typeof existing.state === "object" ? existing.state : {};
|
|
2459
|
+
const alphaStep = steps.find((s) => s.step === "solana_alpha_subscribe" && s.ok);
|
|
2460
|
+
const tier = alphaStep?.details?.tier;
|
|
2461
|
+
const seedFields = {};
|
|
2462
|
+
if (!existingState.walletId && walletId) seedFields.walletId = walletId;
|
|
2463
|
+
if (!existingState.tier && tier) seedFields.tier = tier;
|
|
2464
|
+
if (!existingState.mode) seedFields.mode = "HARDENED";
|
|
2465
|
+
if (!existingState.strategyVersion) seedFields.strategyVersion = "1.0.0";
|
|
2466
|
+
if (Object.keys(seedFields).length > 0) {
|
|
2467
|
+
const merged = { ...existingState, ...seedFields };
|
|
2468
|
+
writeJsonFile(stateFilePath, { agentId: effectiveAgentId, state: merged, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2469
|
+
writeMemoryMd(effectiveAgentId, merged);
|
|
2470
|
+
api.logger.info(`[solana-trader] Seeded identity fields into state: ${Object.keys(seedFields).join(", ")}`);
|
|
2471
|
+
}
|
|
2472
|
+
} catch (seedErr) {
|
|
2473
|
+
api.logger.warn(`[solana-trader] Failed to seed identity fields: ${seedErr instanceof Error ? seedErr.message : String(seedErr)}`);
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2452
2476
|
return {
|
|
2453
2477
|
ok: allOk,
|
|
2454
2478
|
ts: Date.now(),
|
|
@@ -2598,6 +2622,49 @@ ${notes}
|
|
|
2598
2622
|
parameters: Type.Object({}),
|
|
2599
2623
|
execute: wrapExecute("solana_system_status", async () => get("/api/system/status"))
|
|
2600
2624
|
});
|
|
2625
|
+
api.registerTool({
|
|
2626
|
+
name: "solana_storage_status",
|
|
2627
|
+
description: "Check local VPS disk usage and plugin storage health. Reports disk free/total, daily log count + size, candidates count, session directory size. Use in heartbeat Step 0 or risk_audit to detect disk pressure before it causes silent failures.",
|
|
2628
|
+
parameters: Type.Object({}),
|
|
2629
|
+
execute: wrapExecute("solana_storage_status", async () => {
|
|
2630
|
+
const os = await import("os");
|
|
2631
|
+
const result = {};
|
|
2632
|
+
try {
|
|
2633
|
+
const stat = fs.statfsSync(workspaceRoot);
|
|
2634
|
+
const totalGB = Math.round(stat.bsize * stat.blocks / 1024 ** 3 * 100) / 100;
|
|
2635
|
+
const freeGB = Math.round(stat.bsize * stat.bavail / 1024 ** 3 * 100) / 100;
|
|
2636
|
+
const usedPct = Math.round((1 - freeGB / totalGB) * 100);
|
|
2637
|
+
result.disk = { totalGB, freeGB, usedPct, warning: usedPct > 85, critical: usedPct > 95 };
|
|
2638
|
+
} catch {
|
|
2639
|
+
result.disk = { error: "unable to read disk stats" };
|
|
2640
|
+
}
|
|
2641
|
+
try {
|
|
2642
|
+
const logFiles = fs.readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
|
|
2643
|
+
let totalBytes = 0;
|
|
2644
|
+
for (const f of logFiles) {
|
|
2645
|
+
try {
|
|
2646
|
+
totalBytes += fs.statSync(path.join(memoryDir, f)).size;
|
|
2647
|
+
} catch {
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
result.dailyLogs = { count: logFiles.length, totalKB: Math.round(totalBytes / 1024) };
|
|
2651
|
+
} catch {
|
|
2652
|
+
result.dailyLogs = { count: 0, totalKB: 0 };
|
|
2653
|
+
}
|
|
2654
|
+
try {
|
|
2655
|
+
const candidatesPath = intelligenceLab.exportDataset("json");
|
|
2656
|
+
const parsed = JSON.parse(candidatesPath);
|
|
2657
|
+
const labeled = Array.isArray(parsed) ? parsed.filter((c) => c.outcome).length : 0;
|
|
2658
|
+
const total = Array.isArray(parsed) ? parsed.length : 0;
|
|
2659
|
+
result.candidates = { total, labeled, unlabeled: total - labeled };
|
|
2660
|
+
} catch {
|
|
2661
|
+
result.candidates = { total: 0, labeled: 0, unlabeled: 0 };
|
|
2662
|
+
}
|
|
2663
|
+
result.memoryRAM = { rssKB: Math.round(process.memoryUsage().rss / 1024), heapUsedKB: Math.round(process.memoryUsage().heapUsed / 1024) };
|
|
2664
|
+
result.uptime = { systemHours: Math.round(os.uptime() / 3600 * 10) / 10, processHours: Math.round(process.uptime() / 3600 * 10) / 10 };
|
|
2665
|
+
return result;
|
|
2666
|
+
})
|
|
2667
|
+
});
|
|
2601
2668
|
api.registerTool({
|
|
2602
2669
|
name: "solana_startup_gate",
|
|
2603
2670
|
description: "Run the mandatory startup sequence and return deterministic pass/fail results per step. Optionally auto-fixes gateway credentials if gatewayBaseUrl and gatewayToken are present in plugin config. On full pass, includes welcomeMessage. If the only failed step is solana_capital_status (e.g. capital API error), still includes welcomeMessage so the user gets onboarding text; check welcomeNote in that case.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solana-traderclaw",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.84",
|
|
4
4
|
"description": "TraderClaw V1-Upgraded — Solana trading for OpenClaw with intelligence lab, tool envelopes, prompt scrubbing, read-only X social intel, and split skill docs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -215,6 +215,8 @@ HARDENED range: +100–300%. DEGEN range: +200–500%. `percent` = price increas
|
|
|
215
215
|
|
|
216
216
|
**Pre-trade journal FIRST** — call `solana_memory_write` with tag `pre_trade_rationale` BEFORE executing. Also call `solana_decision_log` to record the decision with confidence, sizing rationale, and risk factors.
|
|
217
217
|
|
|
218
|
+
**Source attribution (mandatory):** The `pre_trade_rationale` memory entry MUST include `source: "<how you found this token>"` — one of: `alpha_signal:<source_name>`, `scan_launches`, `scan_hot_pairs`, `bitquery_subscription`, `manual`, `watchlist`. This is required for source trust scoring and strategy evolution to correlate wins/losses with discovery channels.
|
|
219
|
+
|
|
218
220
|
**Prior history check (mandatory):**
|
|
219
221
|
```
|
|
220
222
|
solana_memory_by_token({ tokenAddress: "CA" })
|
|
@@ -308,22 +310,31 @@ Execute exits via `solana_trade_execute` with `side: "sell"`.
|
|
|
308
310
|
|
|
309
311
|
Partial exits → "🔴 PARTIAL EXIT (50%): SYMBOL (CA)"
|
|
310
312
|
|
|
311
|
-
**Post-exit mandatory actions:**
|
|
313
|
+
**Post-exit mandatory actions (ALL required — skipping any is a critical violation):**
|
|
312
314
|
|
|
313
315
|
1. Call `solana_trade_review` for each closed position.
|
|
314
316
|
|
|
315
|
-
2.
|
|
317
|
+
2. **LABEL THE OUTCOME — CRITICAL, DO NOT SKIP:**
|
|
316
318
|
```
|
|
317
319
|
solana_candidate_label_outcome({ id: "CA", outcome: "win|loss|skip|dead_money", pnlPct: X.XX, holdingHours: H })
|
|
318
320
|
```
|
|
319
|
-
|
|
321
|
+
The intelligence lab CANNOT learn without labeled outcomes. An unlabeled exit is wasted data. If you skip this call, the entire learning pipeline is broken — strategy evolution, model evaluation, and replay all depend on labeled candidates.
|
|
322
|
+
|
|
323
|
+
3. **LEARNING ENTRY — REQUIRED after every loss or dead_money exit:**
|
|
324
|
+
```
|
|
325
|
+
solana_memory_write({
|
|
326
|
+
content: "LEARNING ENTRY: LRN-YYYYMMDD-NNN\nPriority: P2\nArea: <area_tag>\nWHAT HAPPENED: <1 sentence>\nWHY IT WENT WRONG: <root cause>\nEVIDENCE: token CA, entry price, exit price, hold time\nSUGGESTED ADJUSTMENT: <what to change>",
|
|
327
|
+
tags: ["learning_entry", "learning_entry_<area>"]
|
|
328
|
+
})
|
|
329
|
+
```
|
|
330
|
+
Losses without learning entries are the #1 reason the strategy fails to evolve. Check `refs/review-learning.md` for the full entry format and area tags.
|
|
320
331
|
|
|
321
|
-
|
|
332
|
+
4. Unsubscribe from Bitquery stream for the exited token:
|
|
322
333
|
```
|
|
323
334
|
solana_bitquery_unsubscribe({ subscriptionId: "<id>" })
|
|
324
335
|
```
|
|
325
336
|
|
|
326
|
-
|
|
337
|
+
5. If this was an alpha-sourced trade, check and record source accuracy:
|
|
327
338
|
```
|
|
328
339
|
solana_alpha_history({ tokenAddress: "CA", limit: 5 })
|
|
329
340
|
```
|
|
@@ -339,6 +350,11 @@ Log the source's accuracy via `solana_memory_write` with tag `source_reputation`
|
|
|
339
350
|
- `solana_team_bulletin_post` with tag `position_update` — post current portfolio state
|
|
340
351
|
- `solana_context_snapshot_write` — write portfolio world-view for bootstrap injection
|
|
341
352
|
|
|
353
|
+
**Self-check before completing Step 8:**
|
|
354
|
+
- Did you exit any positions this cycle? If yes: did you call `solana_candidate_label_outcome` for EACH exit? If not, go back and call it now.
|
|
355
|
+
- Did any exit result in a loss or dead_money? If yes: did you write a `learning_entry` via `solana_memory_write`? If not, write one now.
|
|
356
|
+
- Did you execute any trades this cycle? If yes: does each `pre_trade_rationale` include `source:` attribution? If not, write a correction entry now.
|
|
357
|
+
|
|
342
358
|
Do NOT skip the last three. They are not optional memory — they feed the bootstrap digest that loads into your next session.
|
|
343
359
|
|
|
344
360
|
## STEP 9: REPORT TO USER
|
|
@@ -448,8 +448,19 @@ All learning signals MUST be based on SOL-denominated outcomes.
|
|
|
448
448
|
candidate_label_outcome MUST:
|
|
449
449
|
- reflect realizedPnl sign
|
|
450
450
|
- align with unrealizedReturnPct
|
|
451
|
+
- be called on EVERY exit — no exceptions
|
|
452
|
+
- include pnlPct and holdingHours
|
|
451
453
|
|
|
452
454
|
Any inconsistent labeling is invalid.
|
|
455
|
+
|
|
456
|
+
### Learning Loop Enforcement
|
|
457
|
+
|
|
458
|
+
The learning pipeline has three mandatory outputs per trade lifecycle:
|
|
459
|
+
1. **Entry:** `solana_candidate_write` with source attribution (`source: "alpha_signal:<name>|scan_launches|scan_hot_pairs|..."`)
|
|
460
|
+
2. **Exit:** `solana_candidate_label_outcome` — labels the candidate with the actual outcome
|
|
461
|
+
3. **Loss/dead_money exit:** `solana_memory_write` with tag `learning_entry` — captures the root cause
|
|
462
|
+
|
|
463
|
+
If ANY of these three are missing, the strategy evolution cron cannot function. Without labeled outcomes, the intelligence lab models train on nothing. Without learning entries, the same mistakes repeat indefinitely.
|
|
453
464
|
---
|
|
454
465
|
|
|
455
466
|
## Prompt Injection Protection
|
|
@@ -1,14 +1,154 @@
|
|
|
1
|
-
# Bitquery v2 EAP GraphQL Schema Reference
|
|
1
|
+
# SKILL: Bitquery v2 EAP GraphQL Schema Reference
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
This
|
|
5
|
+
This skill covers the Bitquery v2 EAP (Early Access Program) GraphQL schema for Solana, specifically the two trade cubes and their differences. Use it when calling any Bitquery tool, reading query results, or recovering from a failed query.
|
|
6
6
|
|
|
7
7
|
**Endpoint:** `https://streaming.bitquery.io/graphql` (HTTP and WebSocket)
|
|
8
8
|
**Auth header:** `Authorization: Bearer <BITQUERY_API_KEY>`
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## CRITICAL: Catalog Failure Is NOT a Dead End
|
|
13
|
+
|
|
14
|
+
**You have two separate Bitquery tools:**
|
|
15
|
+
|
|
16
|
+
| Tool | What it does |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `solana_bitquery_catalog` | Runs a pre-built template by path (e.g. `pumpFunHoldersRisk.first100Buyers`) |
|
|
19
|
+
| `solana_bitquery_query` | Runs **any raw GraphQL you write** against the same endpoint |
|
|
20
|
+
|
|
21
|
+
When `solana_bitquery_catalog` fails, **do not stop**. You can always write the query yourself and call `solana_bitquery_query` instead. The schema rules in this file give you everything needed to do that correctly.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Recovery Protocol — Step by Step
|
|
26
|
+
|
|
27
|
+
When a Bitquery call returns an error, follow this decision tree:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
1. Read the error code and message from the tool response.
|
|
31
|
+
2. MEMORY CHECK FIRST: search for prior fixes before writing new ones.
|
|
32
|
+
→ solana_memory_search({ query: "bitquery_fix_applied <template_path_or_error_snippet>", limit: 3 })
|
|
33
|
+
→ If a matching fix exists, apply it directly — skip to step 6.
|
|
34
|
+
3. Classify the error (see table below).
|
|
35
|
+
4. If SCHEMA error → apply the fix from "Common Schema Errors → Fix Map" below
|
|
36
|
+
→ call solana_bitquery_query with corrected GraphQL.
|
|
37
|
+
5. If OPERATIONAL error → retry with backoff (max 2 retries) or skip this data.
|
|
38
|
+
6. If AUTH error → report to user; do not retry.
|
|
39
|
+
7. MEMORY WRITE: after any recovery attempt, record the result:
|
|
40
|
+
→ Success: solana_memory_write with tag "bitquery_fix_applied"
|
|
41
|
+
→ Failure: solana_memory_write with tag "bitquery_recovery_failed"
|
|
42
|
+
→ Repeated failure on same template: solana_memory_write with tag "bitquery_template_broken"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Memory Integration for Recovery
|
|
46
|
+
|
|
47
|
+
**Search before recovering** — always check if you've fixed this error before:
|
|
48
|
+
```
|
|
49
|
+
solana_memory_search({ query: "bitquery_fix_applied", limit: 5 })
|
|
50
|
+
```
|
|
51
|
+
Prior fixes contain the exact corrected query and variables that worked. Reusing them is faster and more reliable than re-deriving the fix.
|
|
52
|
+
|
|
53
|
+
**Write after recovering** — record every fix attempt:
|
|
54
|
+
```
|
|
55
|
+
// Successful fix:
|
|
56
|
+
solana_memory_write({
|
|
57
|
+
content: "Bitquery fix: <template_path> failed with '<error_message>'. Fixed by switching cube from DEXTrades to DEXTradeByTokens. Working query: <corrected_query>. Variables: <variables>.",
|
|
58
|
+
tags: ["bitquery_fix_applied"]
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Failed recovery:
|
|
62
|
+
solana_memory_write({
|
|
63
|
+
content: "Bitquery recovery failed: <template_path> error '<error_message>'. Tried: <what_was_attempted>. Result: still failing.",
|
|
64
|
+
tags: ["bitquery_recovery_failed"]
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Template consistently broken (2+ failures):
|
|
68
|
+
solana_memory_write({
|
|
69
|
+
content: "Bitquery template broken: <template_path> consistently fails with '<error_pattern>'. Recommend using solana_bitquery_query with custom GraphQL instead.",
|
|
70
|
+
tags: ["bitquery_template_broken"]
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This memory loop prevents the agent from re-deriving the same fix every session and creates a growing knowledge base of working query patterns.
|
|
75
|
+
|
|
76
|
+
### Error Classification
|
|
77
|
+
|
|
78
|
+
| Response indicator | Class | Action |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `code: "BITQUERY_QUERY_FAILED"` + message contains `Cannot query field` | SCHEMA | Fix the field/cube in your query (see Fix Map) |
|
|
81
|
+
| `code: "BITQUERY_QUERY_FAILED"` + message contains `Unknown argument` | SCHEMA | Remove unsupported argument (e.g. `groupBy`) |
|
|
82
|
+
| `code: "BITQUERY_QUERY_FAILED"` + message contains `Variable ... is never used` | SCHEMA | Remove the undeclared variable from the query signature |
|
|
83
|
+
| `code: "BITQUERY_QUERY_FAILED"` + message contains `Argument ... has invalid value` | SCHEMA | Fix the WHERE filter shape (wrong cube or wrong field path) |
|
|
84
|
+
| `code: "BITQUERY_QUERY_FAILED"` + message contains `context deadline exceeded` or `aborted` | OPERATIONAL — TIMEOUT | Add `Block: { Time: { since: $since } }` to WHERE; retry once |
|
|
85
|
+
| `code: "BITQUERY_QUERY_FAILED"` + HTTP 429 | OPERATIONAL — RATE LIMIT | Wait 5s and retry once; if still failing, skip and note |
|
|
86
|
+
| `code: "BITQUERY_QUERY_FAILED"` + HTTP 500 | OPERATIONAL — SERVER | Retry once after 3s; if still failing, skip this data point |
|
|
87
|
+
| `code: "BITQUERY_API_KEY_MISSING"` | AUTH | Report to user — no retry possible |
|
|
88
|
+
| Response `ok: false` + no `errors[]` and no HTTP status | NETWORK | Retry once; if failing, skip |
|
|
89
|
+
| Response `ok: true`, `data` present but all arrays are empty | NOT AN ERROR | The query is valid — the token/time window has no data |
|
|
90
|
+
|
|
91
|
+
### Writing a Custom Query to Replace a Failed Catalog Call
|
|
92
|
+
|
|
93
|
+
1. Call `solana_bitquery_templates` to find the closest existing template and read its operation text as a starting point.
|
|
94
|
+
2. Read the error message. Match it to the Fix Map table below.
|
|
95
|
+
3. Apply the fix (change cube, fix field path, remove bad argument, etc.).
|
|
96
|
+
4. Call `solana_bitquery_query` with:
|
|
97
|
+
- `query`: your corrected GraphQL string
|
|
98
|
+
- `variables`: the same variables you were going to pass
|
|
99
|
+
|
|
100
|
+
**Example — catalog call fails with "Cannot query field `Currency` on DEXTrades":**
|
|
101
|
+
```
|
|
102
|
+
// Original catalog call (fails):
|
|
103
|
+
solana_bitquery_catalog({ templatePath: "pumpFunTradesLiquidity.latestTradesByToken", variables: { token: "...", limit: 10 } })
|
|
104
|
+
|
|
105
|
+
// Error: Cannot query field "Currency" on type "Solana_DEXTrade_Fields_Trade"
|
|
106
|
+
// Fix: switch cube from DEXTrades → DEXTradeByTokens (Trade.Currency is valid there)
|
|
107
|
+
|
|
108
|
+
// Recovery call:
|
|
109
|
+
solana_bitquery_query({
|
|
110
|
+
query: `query LatestTradesByToken($token: String!, $limit: Int!) {
|
|
111
|
+
Solana {
|
|
112
|
+
DEXTradeByTokens(
|
|
113
|
+
where: { Trade: { Currency: { MintAddress: { is: $token } } } }
|
|
114
|
+
orderBy: { descending: Block_Time }
|
|
115
|
+
limit: { count: $limit }
|
|
116
|
+
) {
|
|
117
|
+
Block { Time }
|
|
118
|
+
Trade { PriceInUSD Amount AmountInUSD Side { Type } }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}`,
|
|
122
|
+
variables: { token: "...", limit: 10 }
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### When to Give Up vs When to Recover
|
|
127
|
+
|
|
128
|
+
**Always attempt recovery (1 retry with a corrected query):**
|
|
129
|
+
- Any SCHEMA error — you have the knowledge to fix it from this file.
|
|
130
|
+
- TIMEOUT — add a `since` filter and retry.
|
|
131
|
+
|
|
132
|
+
**Skip and continue without this data point:**
|
|
133
|
+
- Two consecutive failures on the same query path.
|
|
134
|
+
- AUTH errors.
|
|
135
|
+
- The data is supplementary (not required for the trade decision).
|
|
136
|
+
|
|
137
|
+
**Block the trade:**
|
|
138
|
+
- FRESH token analysis fails for `first100Buyers` AND `devHoldings` after recovery attempts — risk is unquantifiable.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Catalog vs Live Schema — Why 400s Happen
|
|
143
|
+
|
|
144
|
+
A query path being registered in `OPENCLAW_QUERY_TEMPLATES` (e.g. `pumpFunHoldersRisk.first100Buyers`) only means the **path string** resolves to a GraphQL operation. It does **not** guarantee the operation is schema-valid.
|
|
145
|
+
|
|
146
|
+
When `runCatalogQuery` returns `ok: false` with HTTP 400 (or HTTP 200 with `errors[]`), the stored GraphQL text has a schema violation. Fix it in `SpyFly/Contexts/openClawAPI/bitQuery.js`.
|
|
147
|
+
|
|
148
|
+
The most common root causes are using `DEXTrades`-only fields (`Trade.Currency`, `Trade.Buyer`, `Trade.PriceInUSD`, `Trade.Side`, `Trade.AmountInUSD`) on the wrong cube, or using `groupBy` on `DEXTradeByTokens`.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
12
152
|
## The Two Trade Cubes
|
|
13
153
|
|
|
14
154
|
Bitquery v2 has two Solana trade cubes with **fundamentally different `Trade` shapes**. Mixing them up causes `Cannot query field "X" on type "Solana_DEXTrade_Fields_Trade"` errors.
|
|
@@ -43,11 +183,11 @@ DEXTrades(...) {
|
|
|
43
183
|
```
|
|
44
184
|
|
|
45
185
|
**WHERE filters in DEXTrades:**
|
|
46
|
-
- Filter by Dex: `Trade: { Dex: { ProtocolName: { includes: "pump" } } }`
|
|
47
|
-
- Filter by token (buy side): `Trade: { Buy: { Currency: { MintAddress: { is: $token } } } }`
|
|
48
|
-
- Filter by signer: `Transaction: { Signer: { is: $wallet } }`
|
|
49
|
-
- `Trade: { Currency: { MintAddress: ... } }` — **
|
|
50
|
-
- `Trade: { Buyer: { is: $wallet } }` — **
|
|
186
|
+
- Filter by Dex: `Trade: { Dex: { ProtocolName: { includes: "pump" } } }` ✓
|
|
187
|
+
- Filter by token (buy side): `Trade: { Buy: { Currency: { MintAddress: { is: $token } } } }` ✓
|
|
188
|
+
- Filter by signer: `Transaction: { Signer: { is: $wallet } }` ✓
|
|
189
|
+
- `Trade: { Currency: { MintAddress: ... } }` ✗ — **invalid on DEXTrades**
|
|
190
|
+
- `Trade: { Buyer: { is: $wallet } }` ✗ — **invalid on DEXTrades** — use `Transaction: { Signer: { is: $wallet } }`
|
|
51
191
|
|
|
52
192
|
**Aggregate keys for DEXTrades:**
|
|
53
193
|
- `sum(of: Trade_Buy_AmountInUSD)` — buy-side USD volume
|
|
@@ -79,17 +219,29 @@ DEXTradeByTokens(...) {
|
|
|
79
219
|
```
|
|
80
220
|
|
|
81
221
|
**WHERE filters in DEXTradeByTokens:**
|
|
82
|
-
- Filter by token: `Trade: { Currency: { MintAddress: { is: $token } } }`
|
|
83
|
-
- Filter by side: `Trade: { Side: { Type: { is: buy } } }`
|
|
84
|
-
- Filter by Dex: `Trade: { Dex: { ProtocolName: { includes: "pump" } } }`
|
|
222
|
+
- Filter by token: `Trade: { Currency: { MintAddress: { is: $token } } }` ✓
|
|
223
|
+
- Filter by side: `Trade: { Side: { Type: { is: buy } } }` ✓
|
|
224
|
+
- Filter by Dex: `Trade: { Dex: { ProtocolName: { includes: "pump" } } }` ✓
|
|
85
225
|
|
|
86
226
|
**Aggregate keys for DEXTradeByTokens:**
|
|
87
|
-
- `sum(of: Trade_Side_AmountInUSD)` — total USD volume (NOT `Trade_AmountInUSD`)
|
|
227
|
+
- `sum(of: Trade_Side_AmountInUSD)` — total USD volume (use this, NOT `Trade_AmountInUSD`)
|
|
88
228
|
- `sum(of: Trade_Amount)` — native token amount
|
|
89
|
-
- `count(distinct: Transaction_Signer)` — unique traders (NOT `Trade_Buyer`)
|
|
229
|
+
- `count(distinct: Transaction_Signer)` — unique traders (use this, NOT `Trade_Buyer`)
|
|
90
230
|
- `count(distinct: Transaction_Signer, if: {Trade: {Side: {Type: {is: buy}}}})` — unique buyers
|
|
91
|
-
- `groupBy` is
|
|
92
|
-
- If `groupBy` is removed,
|
|
231
|
+
- `groupBy` is **not supported** on this cube in our current v2 endpoint profile
|
|
232
|
+
- If `groupBy` is removed from a query, remove now-unused variables (e.g. `$intervalSeconds`) from the operation signature and `variableShape` too.
|
|
233
|
+
|
|
234
|
+
**OHLC without groupBy:** return raw time-series rows (limit 500) and compute candles client-side:
|
|
235
|
+
```graphql
|
|
236
|
+
DEXTradeByTokens(
|
|
237
|
+
where: {Block: {Time: {since: $since}}, Trade: {Currency: {MintAddress: {is: $token}}}}
|
|
238
|
+
orderBy: {ascending: Block_Time}
|
|
239
|
+
limit: {count: 500}
|
|
240
|
+
) {
|
|
241
|
+
Block { Time }
|
|
242
|
+
Trade { PriceInUSD Amount AmountInUSD }
|
|
243
|
+
}
|
|
244
|
+
```
|
|
93
245
|
|
|
94
246
|
---
|
|
95
247
|
|
|
@@ -150,6 +302,8 @@ BalanceUpdates(
|
|
|
150
302
|
- `PostBalance` requires aggregation modifier: `PostBalance(maximum: Block_Slot)` to get the latest balance
|
|
151
303
|
- `limitBy` key: use `BalanceUpdate_Account_Token_Owner` (not `BalanceUpdate_Address`)
|
|
152
304
|
- WHERE path: `BalanceUpdate: { Account: { Token: { Owner: { is: $wallet } } } }`
|
|
305
|
+
- For lists of wallets: `Account: { Token: { Owner: { in: $holders } } }`
|
|
306
|
+
- `orderBy` on balance alias: use `BalanceUpdate_balance_maximum` (not `"balance"`)
|
|
153
307
|
|
|
154
308
|
---
|
|
155
309
|
|
|
@@ -157,6 +311,8 @@ BalanceUpdates(
|
|
|
157
311
|
|
|
158
312
|
In `TokenSupplyUpdates`, the currency metadata field is `Uri` (camel-case), not `URI`.
|
|
159
313
|
|
|
314
|
+
Use:
|
|
315
|
+
|
|
160
316
|
```graphql
|
|
161
317
|
TokenSupplyUpdates(
|
|
162
318
|
where: {
|
|
@@ -172,9 +328,12 @@ TokenSupplyUpdates(
|
|
|
172
328
|
}
|
|
173
329
|
```
|
|
174
330
|
|
|
331
|
+
Avoid:
|
|
332
|
+
- `Currency { ... URI }` ✗ (unknown field)
|
|
333
|
+
|
|
175
334
|
---
|
|
176
335
|
|
|
177
|
-
## Common Schema Errors
|
|
336
|
+
## Common Schema Errors → Fix Map
|
|
178
337
|
|
|
179
338
|
| Error message | Root cause | Fix |
|
|
180
339
|
|---|---|---|
|
|
@@ -183,16 +342,19 @@ TokenSupplyUpdates(
|
|
|
183
342
|
| `Cannot query field "PriceInUSD" on type "Solana_DEXTrade_Fields_Trade"` | Using `Trade.PriceInUSD` on `DEXTrades` | Use `Trade.Buy.PriceInUSD` or `Trade.Sell.PriceInUSD` |
|
|
184
343
|
| `Cannot query field "AmountInUSD" on type "Solana_DEXTrade_Fields_Trade"` | Using `Trade.AmountInUSD` on `DEXTrades` | Use `Trade.Buy.Amount` or switch to `DEXTradeByTokens` |
|
|
185
344
|
| `Cannot query field "Buyer" on type "Solana_DEXTrade_Fields_Trade"` | Using `Trade.Buyer` on `DEXTrades` | Use `Trade.Buy.Account.Address` for output; `Transaction.Signer` for WHERE |
|
|
345
|
+
| `In field "Trade": In field "Buyer": Unknown field` | `Trade: { Buyer: { is: $wallet } }` in WHERE on DEXTrades | Use `Transaction: { Signer: { is: $wallet } }` |
|
|
186
346
|
| `Cannot query field "Address" on type "Solana_BalanceUpdate"` | Using `BalanceUpdate.Address` | Use `BalanceUpdate.Account.Token.Owner` (SPL) or `BalanceUpdate.Account.Owner` (SOL) |
|
|
187
|
-
| `Cannot query field "URI"` | Using uppercase `URI` in `TokenSupplyUpdate.Currency` | Use `Uri` |
|
|
188
|
-
| `
|
|
189
|
-
| `Unknown argument "groupBy"
|
|
190
|
-
| `Variable "$intervalSeconds" is never used
|
|
191
|
-
| `
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
347
|
+
| `Cannot query field "URI" on type "Solana_TokenSupplyUpdate_Fields_TokenSupplyUpdate_Currency"` | Using uppercase `URI` in `TokenSupplyUpdate.Currency` | Use `Uri` |
|
|
348
|
+
| `In field "Instruction" -> "Accounts" -> "Address": Unknown field` | Using direct `Accounts.Address` in `Instructions.where` | Use `Accounts: { includes: { Address: { is: $token } } }` |
|
|
349
|
+
| `Unknown argument "groupBy" on field "DEXTradeByTokens" of type "Solana"` | Attempting interval grouping on `DEXTradeByTokens` | Remove `groupBy`; return raw rows and build candles client-side |
|
|
350
|
+
| `Variable "$intervalSeconds" is never used in operation ...` | Query signature still includes interval variable after removing groupBy | Remove `$intervalSeconds` from query args and `variableShape` |
|
|
351
|
+
| `Variable "$wallet" is never used in operation ...` | Variable declared in operation but no field references it | Remove `$wallet` from query args and `variableShape` |
|
|
352
|
+
| `Unexpected metric name or alias to order balance ...` | Ordering by non-existent alias like `"balance"` | Order by concrete metric name returned by engine (e.g. `BalanceUpdate_balance_maximum`) |
|
|
353
|
+
| `Variable "$minCap" of type "Float!" used ... expecting type "String"` | Comparator input type mismatch in token supply filters | Use `String` vars for `PostBalanceInUSD` bound filters in this endpoint profile |
|
|
354
|
+
| `This operation was aborted` / `context deadline exceeded` | Query exceeded request timeout budget — often an unbounded Instructions scan | Add `Block: { Time: { since: $since } }` to the WHERE clause; increase `options.timeoutMs` if needed |
|
|
194
355
|
| `Field "Trade_Buyer" not found` | Aggregate `count(distinct: Trade_Buyer)` | Use `count(distinct: Transaction_Signer)` |
|
|
195
|
-
| `Field "Trade_AmountInUSD" not found` (DEXTradeByTokens) | Wrong aggregate key | Use `Trade_Side_AmountInUSD` |
|
|
356
|
+
| `Field "Trade_AmountInUSD" not found` (in DEXTradeByTokens) | Wrong aggregate key | Use `Trade_Side_AmountInUSD` |
|
|
357
|
+
| `calculate(expression: ...)` not recognized | Unsupported computed field expression | Remove `calculate`; compute derived values client-side instead |
|
|
196
358
|
|
|
197
359
|
---
|
|
198
360
|
|
|
@@ -225,15 +387,19 @@ DEXPools(
|
|
|
225
387
|
|
|
226
388
|
---
|
|
227
389
|
|
|
228
|
-
## Instructions Cube — Account Filters
|
|
390
|
+
## Instructions Cube — Account Filters (Important)
|
|
229
391
|
|
|
230
|
-
For `Solana.Instructions`, account matching in `where.Instruction.Accounts` must use
|
|
392
|
+
For `Solana.Instructions`, account matching in `where.Instruction.Accounts` must use
|
|
393
|
+
`includes`, not direct `Address` equality.
|
|
394
|
+
|
|
395
|
+
Use:
|
|
231
396
|
|
|
232
397
|
```graphql
|
|
233
398
|
Instructions(
|
|
234
399
|
where: {
|
|
400
|
+
Block: { Time: { since: $since } }
|
|
235
401
|
Instruction: {
|
|
236
|
-
Program: { Name: { includes: "pump" } }
|
|
402
|
+
Program: { Name: { includes: "pump" }, Method: { includes: "create" } }
|
|
237
403
|
Accounts: { includes: { Address: { is: $token } } }
|
|
238
404
|
}
|
|
239
405
|
Transaction: { Result: { Success: true } }
|
|
@@ -246,8 +412,13 @@ Instructions(
|
|
|
246
412
|
```
|
|
247
413
|
|
|
248
414
|
Avoid:
|
|
249
|
-
- `Accounts: { Address: { is: $token } }` (invalid shape)
|
|
250
|
-
|
|
415
|
+
- `Accounts: { Address: { is: $token } }` ✗ (invalid shape)
|
|
416
|
+
|
|
417
|
+
Also avoid duplicate keys in one input object:
|
|
418
|
+
- `Program: { Name: ... }` and a second `Program: { Method: ... }` in the same object is **invalid** — GraphQL only keeps the last key.
|
|
419
|
+
- Combine into one: `Program: { Name: ..., Method: ... }` ✓
|
|
420
|
+
|
|
421
|
+
**Always add a `Block.Time.since` filter to Instructions queries** — unbounded Instructions scans (especially with ascending orderBy to find creation events) time out consistently.
|
|
251
422
|
|
|
252
423
|
---
|
|
253
424
|
|
|
@@ -257,7 +428,38 @@ Avoid:
|
|
|
257
428
|
- **DEX filter:** `Trade: { Dex: { ProtocolName: { includes: "pump" } } }`
|
|
258
429
|
- **PumpSwap filter:** `Trade: { Dex: { ProtocolName: { includes: "pumpswap" } } }`
|
|
259
430
|
- **Migration detection:** `Instructions` cube with `Program: { Method: { includes: "migrate" } }`
|
|
260
|
-
- **Bonding curve progress:** Requires `DEXPools` with `Base.PostAmountInUSD`
|
|
431
|
+
- **Bonding curve progress:** Requires `DEXPools` with `Base.PostAmountInUSD` — exact threshold formula requires live testing
|
|
432
|
+
- **First buyers:** Use `DEXTrades` with `Trade: { Buy: { Currency: { MintAddress: { is: $token } } } }` and `orderBy: { ascending: Block_Time }` — output buyer address as `Trade.Buy.Account.Address`
|
|
433
|
+
|
|
434
|
+
### Token Creation — Correct Method Filter
|
|
435
|
+
|
|
436
|
+
Filtering Instructions by `Name: {includes: "pump"}` alone returns **all** pump.fun interactions (buys, sells, fees). To target **only new token launches**, filter by the exact program address **and** method:
|
|
437
|
+
|
|
438
|
+
```graphql
|
|
439
|
+
Instruction: {
|
|
440
|
+
Program: {
|
|
441
|
+
Address: {is: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"}
|
|
442
|
+
Method: {is: "create_v2"}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
In the result, `Accounts[0].Address` is the **token mint** and `Transaction.Signer` is the **dev wallet**. Not all pump.fun mints use the `pump` vanity suffix — do not filter by address suffix.
|
|
448
|
+
|
|
449
|
+
Known pump.fun method values (live-verified, 30-day sample of 2000 instructions):
|
|
450
|
+
| Method | Meaning |
|
|
451
|
+
|---|---|
|
|
452
|
+
| `create_v2` | New token launch — **use this for new token detection** |
|
|
453
|
+
| `CreateEvent` | CPI event emitted alongside `create_v2` (single account, less data) |
|
|
454
|
+
| `buy` / `buy_exact_sol_in` / `buy_exact_quote_in` | Buy trades |
|
|
455
|
+
| `sell` | Sell trade |
|
|
456
|
+
| `BuyEvent` / `SellEvent` / `TradeEvent` | CPI event logs for trades |
|
|
457
|
+
| `collect_creator_fee` / `CollectCreatorFeeEvent` | Creator fee collection |
|
|
458
|
+
| `extend_account` / `ExtendAccountEvent` | Account reallocation |
|
|
459
|
+
| `close_user_volume_accumulator` | Volume accumulator housekeeping |
|
|
460
|
+
| `sync_user_volume_accumulator` / `init_user_volume_accumulator` | Volume accumulator lifecycle |
|
|
461
|
+
|
|
462
|
+
**"Mayhem Mode" does not exist on-chain.** No instruction with `mayhem` in the method name has ever appeared in the pump.fun program. The three catalog templates for it (`trackMayhemModeRealtime`, `currentMayhemModeStatus`, `historicalMayhemModeStatus`) have been removed.
|
|
261
463
|
|
|
262
464
|
---
|
|
263
465
|
|
|
@@ -279,8 +481,8 @@ subscription PumpFunTrades($token: String) {
|
|
|
279
481
|
Block { Time }
|
|
280
482
|
Transaction { Signature }
|
|
281
483
|
Trade {
|
|
282
|
-
Buy { Currency { MintAddress Symbol } PriceInUSD }
|
|
283
|
-
Sell { Currency { MintAddress Symbol }
|
|
484
|
+
Buy { Currency { MintAddress Symbol } PriceInUSD Amount }
|
|
485
|
+
Sell { Currency { MintAddress Symbol } }
|
|
284
486
|
}
|
|
285
487
|
}
|
|
286
488
|
}
|
|
@@ -69,6 +69,16 @@ Used by Steps 0, 5.5, 8.5, 9.
|
|
|
69
69
|
| `strategy_evolution` | Strategy evolution reasoning log (Step 9) |
|
|
70
70
|
| `discovery_filter_evolution` | Discovery filter parameter updates (Step 9) |
|
|
71
71
|
|
|
72
|
+
## Bitquery Recovery Tags
|
|
73
|
+
|
|
74
|
+
Used when Bitquery API calls fail and the agent applies recovery procedures.
|
|
75
|
+
|
|
76
|
+
| Tag | Purpose |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `bitquery_fix_applied` | Successful Bitquery recovery — records error class, fix applied, and working query/variables |
|
|
79
|
+
| `bitquery_recovery_failed` | Recovery attempt failed — records error class, what was tried, and why it didn't work |
|
|
80
|
+
| `bitquery_template_broken` | Template identified as consistently broken — records template path, error pattern, suggested replacement |
|
|
81
|
+
|
|
72
82
|
## System Tags
|
|
73
83
|
|
|
74
84
|
| Tag | Purpose |
|
|
@@ -88,3 +98,4 @@ Used by Steps 0, 5.5, 8.5, 9.
|
|
|
88
98
|
| `coordinated_shill_detected` | Coordinated shill campaign detected |
|
|
89
99
|
| `website_analyzed` | Website legitimacy analysis result |
|
|
90
100
|
| `alpha_source_model` | Personal alpha source trust model |
|
|
101
|
+
| `memory_trim` | Memory trim cron results |
|
|
@@ -37,7 +37,6 @@ Every tool has a mandatory trigger — when the trigger condition is met, you MU
|
|
|
37
37
|
| `solana_wallet_token_balance` | On-chain SPL balance (POST) | Step 0 when position balance seems off; Step 6 for on-chain verification |
|
|
38
38
|
| `solana_sweep_dead_tokens` | Batch-exit losing positions | `dead_money_sweep` cron; Step 0 when multiple dead positions found |
|
|
39
39
|
| `solana_killswitch_status` | Check kill switch state | Step 0 every heartbeat |
|
|
40
|
-
| `solana_killswitch` | Toggle kill switch (enabled + mode) | When consecutive loss limit hit; when user requests |
|
|
41
40
|
| `risk_management_get_default` | Read per-wallet default TP/SL/trailing used when buys omit risk | Before relying on implicit defaults; after user asks about protection |
|
|
42
41
|
| `risk_management_set_default` | Save per-wallet default exit plan for future buys | When user or policy wants custom defaults instead of platform system default |
|
|
43
42
|
| `trade_size_limit_get` | Read max **buy** size (SOL) from wallet `limits` (default 1.5) | Before every buy or when user asks about size limits |
|