solana-traderclaw 1.0.140 → 1.0.144
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.
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
// src/http-client.ts
|
|
2
2
|
import kayba, { SpanType } from "@kayba_ai/tracing";
|
|
3
|
+
var OrchestratorRateLimitError = class extends Error {
|
|
4
|
+
code = "RATE_LIMIT_EXCEEDED";
|
|
5
|
+
retryAfterMs;
|
|
6
|
+
status;
|
|
7
|
+
constructor(message, retryAfterMs, status) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "OrchestratorRateLimitError";
|
|
10
|
+
this.retryAfterMs = retryAfterMs;
|
|
11
|
+
this.status = status;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
function parseRetryAfterMs(headerValue, maxMs) {
|
|
15
|
+
if (!headerValue) return null;
|
|
16
|
+
const trimmed = headerValue.trim();
|
|
17
|
+
if (trimmed === "") return null;
|
|
18
|
+
const asSeconds = Number(trimmed);
|
|
19
|
+
if (Number.isFinite(asSeconds) && asSeconds >= 0) {
|
|
20
|
+
return Math.min(Math.round(asSeconds * 1e3), maxMs);
|
|
21
|
+
}
|
|
22
|
+
const asDate = Date.parse(trimmed);
|
|
23
|
+
if (Number.isFinite(asDate)) {
|
|
24
|
+
return Math.max(0, Math.min(asDate - Date.now(), maxMs));
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function sleep(ms) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const t = setTimeout(resolve, ms);
|
|
31
|
+
if (t && typeof t === "object" && "unref" in t) {
|
|
32
|
+
t.unref();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
3
36
|
async function orchestratorRequest(opts) {
|
|
4
37
|
if (!kayba.isConfigured()) {
|
|
5
38
|
return doRequest(opts);
|
|
@@ -21,7 +54,7 @@ async function orchestratorRequest(opts) {
|
|
|
21
54
|
throw err;
|
|
22
55
|
}
|
|
23
56
|
}
|
|
24
|
-
async function doRequest(opts, isRetry = false) {
|
|
57
|
+
async function doRequest(opts, isRetry = false, rateLimitAttempt = 0) {
|
|
25
58
|
const url = `${opts.baseUrl.replace(/\/$/, "")}${opts.path}`;
|
|
26
59
|
const controller = new AbortController();
|
|
27
60
|
const timeoutId = setTimeout(
|
|
@@ -64,7 +97,25 @@ async function doRequest(opts, isRetry = false) {
|
|
|
64
97
|
if ((res.status === 401 || res.status === 403) && !isRetry && opts.onUnauthorized) {
|
|
65
98
|
clearTimeout(timeoutId);
|
|
66
99
|
const newToken = await opts.onUnauthorized();
|
|
67
|
-
return doRequest({ ...opts, accessToken: newToken }, true);
|
|
100
|
+
return doRequest({ ...opts, accessToken: newToken }, true, rateLimitAttempt);
|
|
101
|
+
}
|
|
102
|
+
const bodyCodeIsRateLimit = typeof dataObj?.code === "string" && /\bRATE_LIMIT\b/i.test(dataObj.code);
|
|
103
|
+
const isRateLimited = res.status === 429 || !res.ok && bodyCodeIsRateLimit;
|
|
104
|
+
if (isRateLimited) {
|
|
105
|
+
clearTimeout(timeoutId);
|
|
106
|
+
const maxWait = opts.rateLimitMaxRetryWaitMs ?? 3e4;
|
|
107
|
+
const maxRetries = opts.rateLimitMaxRetries ?? 1;
|
|
108
|
+
const retryAfter = parseRetryAfterMs(res.headers.get("Retry-After"), maxWait) ?? Math.min(maxWait, 1e3 * Math.pow(2, rateLimitAttempt));
|
|
109
|
+
const bodyMessage = typeof dataObj?.message === "string" ? dataObj.message : typeof dataObj?.error === "string" ? dataObj.error : `HTTP ${res.status}`;
|
|
110
|
+
if (rateLimitAttempt < maxRetries) {
|
|
111
|
+
await sleep(retryAfter);
|
|
112
|
+
return doRequest(opts, isRetry, rateLimitAttempt + 1);
|
|
113
|
+
}
|
|
114
|
+
throw new OrchestratorRateLimitError(
|
|
115
|
+
`RATE_LIMIT_EXCEEDED: ${bodyMessage}`,
|
|
116
|
+
retryAfter,
|
|
117
|
+
res.status
|
|
118
|
+
);
|
|
68
119
|
}
|
|
69
120
|
if (!res.ok) {
|
|
70
121
|
const errBody = data && typeof data === "object" && !Array.isArray(data) ? data : null;
|
|
@@ -87,5 +138,6 @@ async function doRequest(opts, isRetry = false) {
|
|
|
87
138
|
}
|
|
88
139
|
|
|
89
140
|
export {
|
|
141
|
+
OrchestratorRateLimitError,
|
|
90
142
|
orchestratorRequest
|
|
91
143
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
readRecoverySecretFromDisk
|
|
3
|
-
writeRecoverySecretToOpenclawAtomic,
|
|
4
|
-
writeRefreshTokenToOpenclawAtomic
|
|
2
|
+
readRecoverySecretFromDisk
|
|
5
3
|
} from "./chunk-IAQC34O7.js";
|
|
6
4
|
import {
|
|
7
5
|
SessionManager,
|
|
@@ -30,7 +28,7 @@ import {
|
|
|
30
28
|
} from "./chunk-R24UDHQG.js";
|
|
31
29
|
import {
|
|
32
30
|
orchestratorRequest
|
|
33
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-E7QOPXSU.js";
|
|
34
32
|
import {
|
|
35
33
|
IntelligenceLab
|
|
36
34
|
} from "./chunk-FBS5FGW2.js";
|
|
@@ -775,6 +773,26 @@ function registerWebFetchTool(api, Type2, logPrefix, options) {
|
|
|
775
773
|
}
|
|
776
774
|
|
|
777
775
|
// index.ts
|
|
776
|
+
var SOLANA_TRADER_LIFECYCLE_SINGLETON_KEY = Symbol.for(
|
|
777
|
+
"openclaw.solana-trader.lifecycle.v1"
|
|
778
|
+
);
|
|
779
|
+
var __solanaTraderGlobalSingletonHolder = globalThis;
|
|
780
|
+
function __solanaTraderDisposePreviousLifecycle(logger) {
|
|
781
|
+
const prev = __solanaTraderGlobalSingletonHolder[SOLANA_TRADER_LIFECYCLE_SINGLETON_KEY];
|
|
782
|
+
if (!prev) return;
|
|
783
|
+
__solanaTraderGlobalSingletonHolder[SOLANA_TRADER_LIFECYCLE_SINGLETON_KEY] = void 0;
|
|
784
|
+
try {
|
|
785
|
+
logger?.info("[solana-trader] Disposing previous plugin lifecycle before re-register");
|
|
786
|
+
prev.dispose();
|
|
787
|
+
} catch (err) {
|
|
788
|
+
logger?.warn(
|
|
789
|
+
`[solana-trader] Previous lifecycle dispose error: ${err instanceof Error ? err.message : String(err)}`
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function __solanaTraderSetCurrentLifecycle(lc) {
|
|
794
|
+
__solanaTraderGlobalSingletonHolder[SOLANA_TRADER_LIFECYCLE_SINGLETON_KEY] = lc;
|
|
795
|
+
}
|
|
778
796
|
var TRADERCLAW_WALLET_PRIVATE_KEY_ENV = "TRADERCLAW_WALLET_PRIVATE_KEY";
|
|
779
797
|
function walletPrivateKeyFromPluginConfigRecord(obj) {
|
|
780
798
|
const w = typeof obj.walletPrivateKey === "string" ? obj.walletPrivateKey.trim() : "";
|
|
@@ -923,6 +941,8 @@ var solanaTraderPlugin = {
|
|
|
923
941
|
name: "Solana Trader",
|
|
924
942
|
description: "Autonomous Solana memecoin trading agent \u2014 V1-Upgraded with intelligence lab, tool envelopes, prompt scrubbing, and split skill architecture",
|
|
925
943
|
register(api) {
|
|
944
|
+
__solanaTraderDisposePreviousLifecycle(api.logger);
|
|
945
|
+
const __solanaTraderDisposers = [];
|
|
926
946
|
const pluginConfigRaw = api.pluginConfig && typeof api.pluginConfig === "object" && !Array.isArray(api.pluginConfig) ? api.pluginConfig : {};
|
|
927
947
|
const walletPrivateKeyFromPluginJsonOnly = walletPrivateKeyFromPluginConfigRecord(pluginConfigRaw);
|
|
928
948
|
const config = parseConfig(api.pluginConfig);
|
|
@@ -1005,14 +1025,6 @@ var solanaTraderPlugin = {
|
|
|
1005
1025
|
`[solana-trader] Failed to write rotated recovery secret to sidecar: ${err instanceof Error ? err.message : String(err)}`
|
|
1006
1026
|
);
|
|
1007
1027
|
}
|
|
1008
|
-
try {
|
|
1009
|
-
writeRecoverySecretToOpenclawAtomic(newSecret);
|
|
1010
|
-
api.logger.info("[solana-trader] Persisted rotated recovery secret to openclaw.json");
|
|
1011
|
-
} catch (err) {
|
|
1012
|
-
api.logger.warn(
|
|
1013
|
-
`[solana-trader] Failed to write rotated recovery secret to openclaw.json: ${err instanceof Error ? err.message : String(err)}`
|
|
1014
|
-
);
|
|
1015
|
-
}
|
|
1016
1028
|
},
|
|
1017
1029
|
clientLabel: "openclaw-plugin-runtime",
|
|
1018
1030
|
timeout: apiTimeout,
|
|
@@ -1035,14 +1047,6 @@ var solanaTraderPlugin = {
|
|
|
1035
1047
|
`[solana-trader] Failed to persist session sidecar: ${err instanceof Error ? err.message : String(err)}`
|
|
1036
1048
|
);
|
|
1037
1049
|
}
|
|
1038
|
-
try {
|
|
1039
|
-
writeRefreshTokenToOpenclawAtomic(tokens.refreshToken);
|
|
1040
|
-
api.logger.info("[solana-trader] Persisted rotated refresh token to openclaw.json");
|
|
1041
|
-
} catch (err) {
|
|
1042
|
-
api.logger.warn(
|
|
1043
|
-
`[solana-trader] Failed to write rotated refresh token to openclaw.json: ${err instanceof Error ? err.message : String(err)}`
|
|
1044
|
-
);
|
|
1045
|
-
}
|
|
1046
1050
|
},
|
|
1047
1051
|
logger: {
|
|
1048
1052
|
info: (msg) => api.logger.info(`[solana-trader] ${msg}`),
|
|
@@ -1050,6 +1054,12 @@ var solanaTraderPlugin = {
|
|
|
1050
1054
|
error: (msg) => api.logger.error(`[solana-trader] ${msg}`)
|
|
1051
1055
|
}
|
|
1052
1056
|
});
|
|
1057
|
+
__solanaTraderDisposers.push(() => {
|
|
1058
|
+
try {
|
|
1059
|
+
sessionManager.destroy();
|
|
1060
|
+
} catch {
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1053
1063
|
const onUnauthorized = async () => {
|
|
1054
1064
|
api.logger.warn("[solana-trader] Received 401 \u2014 refreshing session...");
|
|
1055
1065
|
return sessionManager.handleUnauthorized();
|
|
@@ -2435,6 +2445,12 @@ ${notes}
|
|
|
2435
2445
|
error: (msg) => api.logger.error(`[solana-trader] ${msg}`)
|
|
2436
2446
|
}
|
|
2437
2447
|
});
|
|
2448
|
+
__solanaTraderDisposers.push(() => {
|
|
2449
|
+
try {
|
|
2450
|
+
bitqueryStreamManager.close();
|
|
2451
|
+
} catch {
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2438
2454
|
api.registerTool({
|
|
2439
2455
|
name: "solana_bitquery_subscribe",
|
|
2440
2456
|
description: "Subscribe to a managed real-time Bitquery data stream. The orchestrator manages the WebSocket connection and broadcasts events. Available templates: realtimeTokenPricesSolana, ohlc1s, dexPoolLiquidityChanges, pumpFunTokenCreation, pumpFunTrades, pumpSwapTrades, raydiumNewPools. Returns a subscriptionId for tracking. Pass agentId to enable event-to-agent forwarding \u2014 orchestrator delivers each event to your Gateway via /v1/responses in addition to normal WS delivery. Subscriptions expire after 24h and emit subscription_expiring/subscription_expired events. See websocket-streaming.md in the solana-trader skill for the full message contract and usage patterns.",
|
|
@@ -2568,6 +2584,12 @@ ${notes}
|
|
|
2568
2584
|
error: (msg) => api.logger.error(`[solana-trader] ${msg}`)
|
|
2569
2585
|
}
|
|
2570
2586
|
});
|
|
2587
|
+
__solanaTraderDisposers.push(() => {
|
|
2588
|
+
try {
|
|
2589
|
+
void alphaStreamManager.unsubscribe().catch(() => void 0);
|
|
2590
|
+
} catch {
|
|
2591
|
+
}
|
|
2592
|
+
});
|
|
2571
2593
|
let startupGateRunning = null;
|
|
2572
2594
|
let startupGateState = { ok: false, ts: 0, steps: [] };
|
|
2573
2595
|
let lastForwardProbeState = null;
|
|
@@ -4297,11 +4319,30 @@ Context compaction triggered. STATE.md synced from last persisted state. Decisio
|
|
|
4297
4319
|
}
|
|
4298
4320
|
);
|
|
4299
4321
|
let solanaTraderSessionWatchdogTimer = null;
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4322
|
+
let solanaTraderLifecycleDisposed = false;
|
|
4323
|
+
__solanaTraderDisposers.push(() => {
|
|
4324
|
+
solanaTraderLifecycleDisposed = true;
|
|
4325
|
+
});
|
|
4326
|
+
__solanaTraderDisposers.push(() => {
|
|
4327
|
+
if (solanaTraderSessionWatchdogTimer !== null) {
|
|
4328
|
+
clearInterval(solanaTraderSessionWatchdogTimer);
|
|
4329
|
+
solanaTraderSessionWatchdogTimer = null;
|
|
4330
|
+
}
|
|
4331
|
+
});
|
|
4332
|
+
let solanaTraderBootstrapPromise = null;
|
|
4333
|
+
const bootstrapSessionAndArmWatchdog = () => {
|
|
4334
|
+
if (solanaTraderBootstrapPromise) return solanaTraderBootstrapPromise;
|
|
4335
|
+
solanaTraderBootstrapPromise = (async () => {
|
|
4336
|
+
const checkDisposed = (stage) => {
|
|
4337
|
+
if (solanaTraderLifecycleDisposed) {
|
|
4338
|
+
api.logger.info(`[solana-trader] Bootstrap aborted at ${stage}: lifecycle disposed by newer register`);
|
|
4339
|
+
return true;
|
|
4340
|
+
}
|
|
4341
|
+
return false;
|
|
4342
|
+
};
|
|
4303
4343
|
try {
|
|
4304
4344
|
await sessionManager.initialize();
|
|
4345
|
+
if (checkDisposed("after sessionManager.initialize")) return;
|
|
4305
4346
|
const info = sessionManager.getSessionInfo();
|
|
4306
4347
|
api.logger.info(
|
|
4307
4348
|
`[solana-trader] Session active. Tier: ${info.tier}, Scopes: ${info.scopes.join(", ")}`
|
|
@@ -4323,6 +4364,7 @@ Context compaction triggered. STATE.md synced from last persisted state. Decisio
|
|
|
4323
4364
|
timeout: 5e3,
|
|
4324
4365
|
accessToken: await sessionManager.getAccessToken()
|
|
4325
4366
|
});
|
|
4367
|
+
if (checkDisposed("after healthz")) return;
|
|
4326
4368
|
api.logger.info(`[solana-trader] Orchestrator healthz OK at ${orchestratorUrl}`);
|
|
4327
4369
|
if (healthz && typeof healthz === "object") {
|
|
4328
4370
|
const h = healthz;
|
|
@@ -4331,8 +4373,10 @@ Context compaction triggered. STATE.md synced from last persisted state. Decisio
|
|
|
4331
4373
|
} catch (err) {
|
|
4332
4374
|
api.logger.warn(`[solana-trader] /healthz unreachable at ${orchestratorUrl}: ${err instanceof Error ? err.message : String(err)}`);
|
|
4333
4375
|
}
|
|
4376
|
+
if (checkDisposed("after healthz catch")) return;
|
|
4334
4377
|
try {
|
|
4335
4378
|
const status = await get("/api/system/status");
|
|
4379
|
+
if (checkDisposed("after /api/system/status")) return;
|
|
4336
4380
|
api.logger.info(`[solana-trader] Connected to orchestrator (walletId: ${walletId})`);
|
|
4337
4381
|
if (status && typeof status === "object") {
|
|
4338
4382
|
api.logger.info(`[solana-trader] System status: ${JSON.stringify(status)}`);
|
|
@@ -4340,8 +4384,10 @@ Context compaction triggered. STATE.md synced from last persisted state. Decisio
|
|
|
4340
4384
|
} catch (err) {
|
|
4341
4385
|
api.logger.warn(`[solana-trader] /api/system/status unreachable: ${err instanceof Error ? err.message : String(err)}`);
|
|
4342
4386
|
}
|
|
4387
|
+
if (checkDisposed("after system-status catch")) return;
|
|
4343
4388
|
try {
|
|
4344
4389
|
const startupGate = await runStartupGate({ autoFixGateway: true, force: true });
|
|
4390
|
+
if (checkDisposed("after startup gate")) return;
|
|
4345
4391
|
api.logger.info(`[solana-trader] Startup gate completed: ok=${startupGate.ok}, passed=${startupGate.summary.passed}, failed=${startupGate.summary.failed}`);
|
|
4346
4392
|
if (!startupGate.ok) {
|
|
4347
4393
|
api.logger.warn(`[solana-trader] Startup gate failures: ${JSON.stringify(startupGate.steps.filter((step) => !step.ok))}`);
|
|
@@ -4349,12 +4395,15 @@ Context compaction triggered. STATE.md synced from last persisted state. Decisio
|
|
|
4349
4395
|
} catch (err) {
|
|
4350
4396
|
api.logger.warn(`[solana-trader] Startup gate run failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4351
4397
|
}
|
|
4398
|
+
if (checkDisposed("after startup gate catch")) return;
|
|
4352
4399
|
try {
|
|
4353
4400
|
const probe = await runForwardProbe({ agentId: config.agentId || "main", source: "service_startup" });
|
|
4401
|
+
if (checkDisposed("after forward probe")) return;
|
|
4354
4402
|
api.logger.info(`[solana-trader] Forward probe result: ${JSON.stringify(probe)}`);
|
|
4355
4403
|
} catch (err) {
|
|
4356
4404
|
api.logger.warn(`[solana-trader] Forward probe failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4357
4405
|
}
|
|
4406
|
+
if (checkDisposed("before watchdog arm")) return;
|
|
4358
4407
|
const WATCHDOG_INTERVAL_MS = 20 * 60 * 1e3;
|
|
4359
4408
|
const FORWARD_PROBE_WATCHDOG_MS = 7 * 60 * 1e3;
|
|
4360
4409
|
const STARTUP_GATE_AFTER_PROBE_FAIL_COOLDOWN_MS = 10 * 60 * 1e3;
|
|
@@ -4454,6 +4503,18 @@ Context compaction triggered. STATE.md synced from last persisted state. Decisio
|
|
|
4454
4503
|
if (solanaTraderSessionWatchdogTimer && typeof solanaTraderSessionWatchdogTimer === "object" && "unref" in solanaTraderSessionWatchdogTimer) {
|
|
4455
4504
|
solanaTraderSessionWatchdogTimer.unref();
|
|
4456
4505
|
}
|
|
4506
|
+
if (solanaTraderLifecycleDisposed && solanaTraderSessionWatchdogTimer !== null) {
|
|
4507
|
+
clearInterval(solanaTraderSessionWatchdogTimer);
|
|
4508
|
+
solanaTraderSessionWatchdogTimer = null;
|
|
4509
|
+
api.logger.info("[solana-trader] Bootstrap cleared freshly-armed watchdog: lifecycle disposed during setInterval window");
|
|
4510
|
+
}
|
|
4511
|
+
})();
|
|
4512
|
+
return solanaTraderBootstrapPromise;
|
|
4513
|
+
};
|
|
4514
|
+
api.registerService({
|
|
4515
|
+
id: "solana-trader-session",
|
|
4516
|
+
start: async () => {
|
|
4517
|
+
await bootstrapSessionAndArmWatchdog();
|
|
4457
4518
|
},
|
|
4458
4519
|
stop: async () => {
|
|
4459
4520
|
if (solanaTraderSessionWatchdogTimer !== null) {
|
|
@@ -4547,6 +4608,21 @@ Context compaction triggered. STATE.md synced from last persisted state. Decisio
|
|
|
4547
4608
|
api.logger.info(
|
|
4548
4609
|
`[solana-trader] V1-Upgraded-Public: Registered ${totalToolCount} tools (${baseToolCount} base + ${intelligenceToolCount} intelligence + ${webFetchCount} web_fetch = ${totalRegistered} Solana + ${xToolCount} X/Twitter read-only) for walletId ${walletId} (session auth mode)`
|
|
4549
4610
|
);
|
|
4611
|
+
__solanaTraderSetCurrentLifecycle({
|
|
4612
|
+
dispose: () => {
|
|
4613
|
+
for (let i = __solanaTraderDisposers.length - 1; i >= 0; i--) {
|
|
4614
|
+
try {
|
|
4615
|
+
__solanaTraderDisposers[i]();
|
|
4616
|
+
} catch {
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
});
|
|
4621
|
+
void bootstrapSessionAndArmWatchdog().catch((err) => {
|
|
4622
|
+
api.logger.error(
|
|
4623
|
+
`[solana-trader] Post-register bootstrap failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4624
|
+
);
|
|
4625
|
+
});
|
|
4550
4626
|
}
|
|
4551
4627
|
};
|
|
4552
4628
|
var index_default = solanaTraderPlugin;
|
package/dist/src/http-client.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solana-traderclaw",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.144",
|
|
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",
|