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-6GSGHMUH.js";
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
- api.registerService({
4301
- id: "solana-trader-session",
4302
- start: async () => {
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;
@@ -1,6 +1,8 @@
1
1
  import {
2
+ OrchestratorRateLimitError,
2
3
  orchestratorRequest
3
- } from "../chunk-6GSGHMUH.js";
4
+ } from "../chunk-E7QOPXSU.js";
4
5
  export {
6
+ OrchestratorRateLimitError,
5
7
  orchestratorRequest
6
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solana-traderclaw",
3
- "version": "1.0.140",
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",