solana-traderclaw 1.0.140 → 1.0.141

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
@@ -30,7 +30,7 @@ import {
30
30
  } from "./chunk-R24UDHQG.js";
31
31
  import {
32
32
  orchestratorRequest
33
- } from "./chunk-6GSGHMUH.js";
33
+ } from "./chunk-E7QOPXSU.js";
34
34
  import {
35
35
  IntelligenceLab
36
36
  } from "./chunk-FBS5FGW2.js";
@@ -775,6 +775,26 @@ function registerWebFetchTool(api, Type2, logPrefix, options) {
775
775
  }
776
776
 
777
777
  // index.ts
778
+ var SOLANA_TRADER_LIFECYCLE_SINGLETON_KEY = Symbol.for(
779
+ "openclaw.solana-trader.lifecycle.v1"
780
+ );
781
+ var __solanaTraderGlobalSingletonHolder = globalThis;
782
+ function __solanaTraderDisposePreviousLifecycle(logger) {
783
+ const prev = __solanaTraderGlobalSingletonHolder[SOLANA_TRADER_LIFECYCLE_SINGLETON_KEY];
784
+ if (!prev) return;
785
+ __solanaTraderGlobalSingletonHolder[SOLANA_TRADER_LIFECYCLE_SINGLETON_KEY] = void 0;
786
+ try {
787
+ logger?.info("[solana-trader] Disposing previous plugin lifecycle before re-register");
788
+ prev.dispose();
789
+ } catch (err) {
790
+ logger?.warn(
791
+ `[solana-trader] Previous lifecycle dispose error: ${err instanceof Error ? err.message : String(err)}`
792
+ );
793
+ }
794
+ }
795
+ function __solanaTraderSetCurrentLifecycle(lc) {
796
+ __solanaTraderGlobalSingletonHolder[SOLANA_TRADER_LIFECYCLE_SINGLETON_KEY] = lc;
797
+ }
778
798
  var TRADERCLAW_WALLET_PRIVATE_KEY_ENV = "TRADERCLAW_WALLET_PRIVATE_KEY";
779
799
  function walletPrivateKeyFromPluginConfigRecord(obj) {
780
800
  const w = typeof obj.walletPrivateKey === "string" ? obj.walletPrivateKey.trim() : "";
@@ -923,6 +943,8 @@ var solanaTraderPlugin = {
923
943
  name: "Solana Trader",
924
944
  description: "Autonomous Solana memecoin trading agent \u2014 V1-Upgraded with intelligence lab, tool envelopes, prompt scrubbing, and split skill architecture",
925
945
  register(api) {
946
+ __solanaTraderDisposePreviousLifecycle(api.logger);
947
+ const __solanaTraderDisposers = [];
926
948
  const pluginConfigRaw = api.pluginConfig && typeof api.pluginConfig === "object" && !Array.isArray(api.pluginConfig) ? api.pluginConfig : {};
927
949
  const walletPrivateKeyFromPluginJsonOnly = walletPrivateKeyFromPluginConfigRecord(pluginConfigRaw);
928
950
  const config = parseConfig(api.pluginConfig);
@@ -1050,6 +1072,12 @@ var solanaTraderPlugin = {
1050
1072
  error: (msg) => api.logger.error(`[solana-trader] ${msg}`)
1051
1073
  }
1052
1074
  });
1075
+ __solanaTraderDisposers.push(() => {
1076
+ try {
1077
+ sessionManager.destroy();
1078
+ } catch {
1079
+ }
1080
+ });
1053
1081
  const onUnauthorized = async () => {
1054
1082
  api.logger.warn("[solana-trader] Received 401 \u2014 refreshing session...");
1055
1083
  return sessionManager.handleUnauthorized();
@@ -2435,6 +2463,12 @@ ${notes}
2435
2463
  error: (msg) => api.logger.error(`[solana-trader] ${msg}`)
2436
2464
  }
2437
2465
  });
2466
+ __solanaTraderDisposers.push(() => {
2467
+ try {
2468
+ bitqueryStreamManager.close();
2469
+ } catch {
2470
+ }
2471
+ });
2438
2472
  api.registerTool({
2439
2473
  name: "solana_bitquery_subscribe",
2440
2474
  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 +2602,12 @@ ${notes}
2568
2602
  error: (msg) => api.logger.error(`[solana-trader] ${msg}`)
2569
2603
  }
2570
2604
  });
2605
+ __solanaTraderDisposers.push(() => {
2606
+ try {
2607
+ void alphaStreamManager.unsubscribe().catch(() => void 0);
2608
+ } catch {
2609
+ }
2610
+ });
2571
2611
  let startupGateRunning = null;
2572
2612
  let startupGateState = { ok: false, ts: 0, steps: [] };
2573
2613
  let lastForwardProbeState = null;
@@ -4297,6 +4337,12 @@ Context compaction triggered. STATE.md synced from last persisted state. Decisio
4297
4337
  }
4298
4338
  );
4299
4339
  let solanaTraderSessionWatchdogTimer = null;
4340
+ __solanaTraderDisposers.push(() => {
4341
+ if (solanaTraderSessionWatchdogTimer !== null) {
4342
+ clearInterval(solanaTraderSessionWatchdogTimer);
4343
+ solanaTraderSessionWatchdogTimer = null;
4344
+ }
4345
+ });
4300
4346
  api.registerService({
4301
4347
  id: "solana-trader-session",
4302
4348
  start: async () => {
@@ -4547,6 +4593,16 @@ Context compaction triggered. STATE.md synced from last persisted state. Decisio
4547
4593
  api.logger.info(
4548
4594
  `[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
4595
  );
4596
+ __solanaTraderSetCurrentLifecycle({
4597
+ dispose: () => {
4598
+ for (let i = __solanaTraderDisposers.length - 1; i >= 0; i--) {
4599
+ try {
4600
+ __solanaTraderDisposers[i]();
4601
+ } catch {
4602
+ }
4603
+ }
4604
+ }
4605
+ });
4550
4606
  }
4551
4607
  };
4552
4608
  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.141",
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",