@hyperflow.fun/ghost 0.0.3 → 0.0.4

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.
Files changed (37) hide show
  1. package/README.md +4 -20
  2. package/dist/index.js +394 -249
  3. package/dist/package.json +1 -1
  4. package/dist/skills/builtin/event-judge/SKILL.md +1 -0
  5. package/dist/web/dist/assets/{Chart-BOyhyITw.js → Chart-BgUhYrVH.js} +1 -1
  6. package/dist/web/dist/assets/{Config-CQyc8yqV.js → Config-CMp84sNc.js} +1 -1
  7. package/dist/web/dist/assets/{Cost-BoAvnlDi.js → Cost-BgKdnj4i.js} +1 -1
  8. package/dist/web/dist/assets/{Cron-5mIZVsft.js → Cron-L4GEQwj0.js} +1 -1
  9. package/dist/web/dist/assets/{Dashboard-BRxSDtCD.js → Dashboard-D1UNPhTW.js} +1 -1
  10. package/dist/web/dist/assets/{Logs-BpXqElSx.js → Logs-DZkot-wQ.js} +1 -1
  11. package/dist/web/dist/assets/{Memory-CcgSgCqJ.js → Memory-DsQackdo.js} +1 -1
  12. package/dist/web/dist/assets/SVN-RethinkSans-Bold-BIisIh3U.otf +0 -0
  13. package/dist/web/dist/assets/SVN-RethinkSans-Italic-8Q-lLPN5.otf +0 -0
  14. package/dist/web/dist/assets/SVN-RethinkSans-Medium-yLh4RBRv.otf +0 -0
  15. package/dist/web/dist/assets/SVN-RethinkSans-Regular-CVuIF6ni.otf +0 -0
  16. package/dist/web/dist/assets/{Sessions-BEIP55fW.js → Sessions-uqRIDn57.js} +1 -1
  17. package/dist/web/dist/assets/{Skills-4s4LDpzh.js → Skills-B9WQPgSe.js} +1 -1
  18. package/dist/web/dist/assets/{Tools-C-NcyONi.js → Tools-Dyf8Zp-H.js} +1 -1
  19. package/dist/web/dist/assets/{activity-DJy9m_N9.js → activity-DRn1WSJo.js} +1 -1
  20. package/dist/web/dist/assets/{clock-CFVWqojO.js → clock-FMsaqykN.js} +1 -1
  21. package/dist/web/dist/assets/empty-notifications-C88Hm9lC.svg +62 -0
  22. package/dist/web/dist/assets/empty-token-search-aBFBwedz.svg +5 -0
  23. package/dist/web/dist/assets/{highlighted-body-OFNGDK62-y5VVuvTf.js → highlighted-body-OFNGDK62-Ck5_e4wj.js} +1 -1
  24. package/dist/web/dist/assets/index-CGNakXCn.css +1 -0
  25. package/dist/web/dist/assets/index-DNznoLXb.js +54 -0
  26. package/dist/web/dist/assets/{mermaid-GHXKKRXX-BvdvTI5r.js → mermaid-GHXKKRXX-jlxJo7RH.js} +3 -3
  27. package/dist/web/dist/assets/{refresh-cw-DlbQ5OE4.js → refresh-cw-CRQa2SMT.js} +1 -1
  28. package/dist/web/dist/assets/welcome-globe-analytics-sF4OWku9.svg +201 -0
  29. package/dist/web/dist/assets/welcome-globe-bars-COA7MJUP.svg +305 -0
  30. package/dist/web/dist/assets/welcome-news-card-BfM47ZZ3.svg +59 -0
  31. package/dist/web/dist/index.html +2 -2
  32. package/package.json +1 -1
  33. package/dist/web/dist/assets/index-B1ED3P_d.css +0 -1
  34. package/dist/web/dist/assets/index-C6QRLmO2.js +0 -54
  35. package/dist/web/dist/assets/rethink-sans-latin-ext-CuoiHPIp.woff2 +0 -0
  36. package/dist/web/dist/assets/rethink-sans-latin-v3CgWhBT.woff2 +0 -0
  37. package/dist/web/dist/assets/welcome-illustration-B8_SJ7In.svg +0 -319
package/dist/index.js CHANGED
@@ -168638,7 +168638,11 @@ class CronService {
168638
168638
  this.seedDefaultJobs(opts.defaults ?? BUILT_IN_JOBS);
168639
168639
  const now = Date.now();
168640
168640
  for (const job of this.store.jobs) {
168641
- if (job.enabled && !job.state.nextRunAtMs) {
168641
+ if (!job.enabled)
168642
+ continue;
168643
+ if (job.state.nextRunAtMs !== null && job.state.nextRunAtMs <= now && (job.schedule.kind === "cron" || job.schedule.kind === "at")) {
168644
+ job.state.nextRunAtMs = computeNextRun(job.schedule, now);
168645
+ } else if (!job.state.nextRunAtMs) {
168642
168646
  job.state.nextRunAtMs = computeNextRun(job.schedule, now);
168643
168647
  }
168644
168648
  }
@@ -180691,6 +180695,50 @@ var init_cloid = __esm(() => {
180691
180695
  SUFFIX_BYTE_LENGTH = SUFFIX_HEX_LENGTH / 2;
180692
180696
  });
180693
180697
 
180698
+ // src/services/live/info-cache.ts
180699
+ class InfoCache {
180700
+ entries = new Map;
180701
+ ttlMs;
180702
+ constructor(ttlMs = 3000) {
180703
+ this.ttlMs = ttlMs;
180704
+ }
180705
+ get(key, fetcher) {
180706
+ const now = Date.now();
180707
+ const existing = this.entries.get(key);
180708
+ if (existing !== undefined && now - existing.insertedAt < this.ttlMs) {
180709
+ return existing.promise;
180710
+ }
180711
+ const promise6 = fetcher().catch((err) => {
180712
+ if (this.entries.get(key)?.promise === promise6) {
180713
+ this.entries.delete(key);
180714
+ }
180715
+ throw err;
180716
+ });
180717
+ this.entries.set(key, { promise: promise6, insertedAt: now });
180718
+ return promise6;
180719
+ }
180720
+ clear() {
180721
+ this.entries.clear();
180722
+ }
180723
+ }
180724
+ async function runWithConcurrency(items, concurrency, fn) {
180725
+ const results = new Array(items.length);
180726
+ let nextIndex = 0;
180727
+ async function worker() {
180728
+ while (nextIndex < items.length) {
180729
+ const index = nextIndex++;
180730
+ try {
180731
+ results[index] = { status: "fulfilled", value: await fn(items[index], index) };
180732
+ } catch (reason) {
180733
+ results[index] = { status: "rejected", reason };
180734
+ }
180735
+ }
180736
+ }
180737
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker());
180738
+ await Promise.all(workers);
180739
+ return results;
180740
+ }
180741
+
180694
180742
  // src/services/live/client.ts
180695
180743
  function mapFill(f2) {
180696
180744
  return {
@@ -180734,6 +180782,45 @@ function parseDex(symbol5) {
180734
180782
  return { dex: null, name: trimmed };
180735
180783
  return { dex, name: trimmed };
180736
180784
  }
180785
+ function mapPositionsFromRaw(raw) {
180786
+ const positions = [];
180787
+ for (const ap of raw.assetPositions ?? []) {
180788
+ const pos = ap.position;
180789
+ const szi = parseFloat(pos.szi);
180790
+ if (szi === 0)
180791
+ continue;
180792
+ const entryPx = parseFloat(pos.entryPx);
180793
+ const markPx = pos.positionValue ? Math.abs(parseFloat(pos.positionValue) / szi) : entryPx;
180794
+ positions.push({
180795
+ symbol: pos.coin,
180796
+ side: szi > 0 ? "long" : "short",
180797
+ size: Math.abs(szi),
180798
+ entryPrice: entryPx,
180799
+ markPrice: markPx,
180800
+ liquidationPrice: pos.liquidationPx && pos.liquidationPx !== "0.0" && parseFloat(pos.liquidationPx) > 0 ? parseFloat(pos.liquidationPx) : null,
180801
+ unrealizedPnl: parseFloat(pos.unrealizedPnl),
180802
+ unrealizedPnlPct: parseFloat(pos.returnOnEquity ?? "0") * 100,
180803
+ leverage: parseFloat(pos.leverage?.value ?? "1"),
180804
+ marginMode: pos.leverage?.type === "isolated" ? "isolated" : "cross",
180805
+ margin: parseFloat(pos.marginUsed)
180806
+ });
180807
+ }
180808
+ return positions;
180809
+ }
180810
+ function mapOpenOrderFromRaw(o10) {
180811
+ return {
180812
+ orderId: String(o10.oid),
180813
+ symbol: o10.coin,
180814
+ side: o10.side === "B" ? "buy" : "sell",
180815
+ orderType: o10.orderType ?? "Limit",
180816
+ price: o10.limitPx ? parseFloat(o10.limitPx) : null,
180817
+ triggerPrice: o10.triggerPx && o10.triggerPx !== "0.0" ? parseFloat(o10.triggerPx) : null,
180818
+ size: parseFloat(o10.sz),
180819
+ filled: parseFloat(o10.origSz ?? o10.sz) - parseFloat(o10.sz),
180820
+ reduceOnly: o10.reduceOnly ?? false,
180821
+ timestamp: o10.timestamp ?? Date.now()
180822
+ };
180823
+ }
180737
180824
  function ctxToTicker(ctx, symbol5) {
180738
180825
  const markPx = parseFloat(ctx.markPx ?? "0");
180739
180826
  const prevDay = parseFloat(ctx.prevDayPx ?? "0");
@@ -180761,10 +180848,16 @@ class HyperliquidClient {
180761
180848
  maxLeverage = new Map;
180762
180849
  assetNames = [];
180763
180850
  metaLoaded = false;
180851
+ metaInFlight = null;
180764
180852
  dexUniverses = new Map;
180765
180853
  dexListCache = null;
180766
180854
  dexListCacheAt = 0;
180767
180855
  DEX_CACHE_TTL_MS = 60 * 60 * 1000;
180856
+ infoCache = new InfoCache(3000);
180857
+ wsTransport = null;
180858
+ wsSubClient = null;
180859
+ wsLifecycle = null;
180860
+ wsDisposed = false;
180768
180861
  constructor(config3, logger) {
180769
180862
  this.defaultAddress = config3?.address ?? "";
180770
180863
  this.testnet = config3?.testnet ?? false;
@@ -180785,6 +180878,7 @@ class HyperliquidClient {
180785
180878
  connect(config3) {
180786
180879
  this.defaultAddress = config3.address || this.defaultAddress;
180787
180880
  this.baseUrl = config3.testnet ? TESTNET_URL : MAINNET_URL;
180881
+ this.wsDisposed = false;
180788
180882
  if (config3.privateKey) {
180789
180883
  const wallet = privateKeyToAccount(config3.privateKey);
180790
180884
  const transport = new HttpTransport({ isTestnet: config3.testnet });
@@ -180794,18 +180888,35 @@ class HyperliquidClient {
180794
180888
  disconnect() {
180795
180889
  this.defaultAddress = "";
180796
180890
  this.exchange = null;
180891
+ this.closeWs();
180797
180892
  }
180798
180893
  requireExchange() {
180799
180894
  if (!this.exchange)
180800
180895
  throw new Error("Write operations require a private key. Use the connect_wallet tool or set hlPrivateKey in config.");
180801
180896
  return this.exchange;
180802
180897
  }
180803
- async info(type5, extra = {}) {
180898
+ async info(type5, extra = {}, options = {}) {
180899
+ if (!options.cache)
180900
+ return this.fetchInfo(type5, extra, 0);
180901
+ const key = `${type5}:${JSON.stringify(extra)}`;
180902
+ return this.infoCache.get(key, () => this.fetchInfo(type5, extra, 0));
180903
+ }
180904
+ async fetchInfo(type5, extra, attempt) {
180804
180905
  const res = await fetch(`${this.baseUrl}/info`, {
180805
180906
  method: "POST",
180806
180907
  headers: { "Content-Type": "application/json" },
180807
180908
  body: JSON.stringify({ type: type5, ...extra })
180808
180909
  });
180910
+ if (res.status === 429 && attempt < 3) {
180911
+ const retryAfterRaw = res.headers.get("retry-after");
180912
+ const retryAfterSec = retryAfterRaw !== null ? Number(retryAfterRaw) : NaN;
180913
+ const MAX_RETRY_AFTER_MS = 5000;
180914
+ const baseMs = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.min(retryAfterSec * 1000, MAX_RETRY_AFTER_MS) : 250 * Math.pow(2, attempt);
180915
+ const jitterMs = Math.random() * 100;
180916
+ this.log.debug({ type: type5, dex: extra.dex, attempt, delayMs: baseMs + jitterMs }, "rate limited \u2014 retrying after backoff");
180917
+ await new Promise((resolve) => setTimeout(resolve, baseMs + jitterMs));
180918
+ return this.fetchInfo(type5, extra, attempt + 1);
180919
+ }
180809
180920
  if (!res.ok) {
180810
180921
  const text = await res.text();
180811
180922
  throw new Error(`Hyperliquid ${type5}: ${res.status} ${text}`);
@@ -180841,8 +180952,16 @@ class HyperliquidClient {
180841
180952
  const hasNewDex = currentDexes.some((d2) => !knownDexNames.has(d2.name));
180842
180953
  if (!hasNewDex)
180843
180954
  return;
180844
- this.metaLoaded = false;
180845
180955
  }
180956
+ if (this.metaInFlight !== null)
180957
+ return this.metaInFlight;
180958
+ this.metaInFlight = this.rebuildMeta().finally(() => {
180959
+ this.metaInFlight = null;
180960
+ });
180961
+ return this.metaInFlight;
180962
+ }
180963
+ async rebuildMeta() {
180964
+ this.metaLoaded = false;
180846
180965
  let nativeUniverse = [];
180847
180966
  try {
180848
180967
  const nativeData = await this.info("meta");
@@ -180851,7 +180970,7 @@ class HyperliquidClient {
180851
180970
  this.log.warn({ err }, "Native meta fetch failed \u2014 daemon starts degraded (HIP-3 may still load)");
180852
180971
  }
180853
180972
  const dexes = await this.listPerpDexes().catch(() => []);
180854
- const dexResults = await Promise.allSettled(dexes.map((d2) => this.info("meta", { dex: d2.name })));
180973
+ const dexResults = await runWithConcurrency(dexes, 4, (d2) => this.info("meta", { dex: d2.name }));
180855
180974
  let merged = [...nativeUniverse];
180856
180975
  const nativeNames = nativeUniverse.map((a) => a.name);
180857
180976
  this.dexUniverses.set("", nativeNames);
@@ -180875,10 +180994,60 @@ class HyperliquidClient {
180875
180994
  }
180876
180995
  });
180877
180996
  this.metaLoaded = true;
180997
+ this.log.info({ dexCount: dexes.length }, "meta rebuild complete");
180878
180998
  }
180879
180999
  getMaxLeverage(symbol5) {
180880
181000
  return this.maxLeverage.get(this.resolveSymbol(symbol5));
180881
181001
  }
181002
+ getAllAssetNames() {
181003
+ return [...this.assetNames];
181004
+ }
181005
+ isKnownSymbol(symbol5) {
181006
+ return this.assetMap.has(this.resolveSymbol(symbol5));
181007
+ }
181008
+ getDexUniverses() {
181009
+ return this.dexUniverses;
181010
+ }
181011
+ async getSubscriptionClient() {
181012
+ if (this.wsSubClient !== null)
181013
+ return this.wsSubClient;
181014
+ if (this.wsLifecycle !== null)
181015
+ return this.wsLifecycle;
181016
+ this.wsLifecycle = (async () => {
181017
+ if (this.wsDisposed)
181018
+ throw new Error("trading client disposed");
181019
+ const transport = new WebSocketTransport({ isTestnet: this.testnet });
181020
+ const client4 = new SubscriptionClient({ transport });
181021
+ if (this.wsDisposed) {
181022
+ await transport.close().catch(() => {});
181023
+ throw new Error("trading client disposed");
181024
+ }
181025
+ this.wsTransport = transport;
181026
+ this.wsSubClient = client4;
181027
+ return client4;
181028
+ })().finally(() => {
181029
+ this.wsLifecycle = null;
181030
+ });
181031
+ return this.wsLifecycle;
181032
+ }
181033
+ async subscribeAllDexsAssetCtxs(listener) {
181034
+ const client4 = await this.getSubscriptionClient();
181035
+ const sub = await client4.allDexsAssetCtxs((e2) => {
181036
+ listener({ ctxs: e2.ctxs });
181037
+ });
181038
+ return { unsubscribe: () => sub.unsubscribe() };
181039
+ }
181040
+ async closeWs() {
181041
+ this.wsDisposed = true;
181042
+ if (this.wsTransport !== null) {
181043
+ try {
181044
+ await this.wsTransport.close();
181045
+ } catch {}
181046
+ }
181047
+ this.wsTransport = null;
181048
+ this.wsSubClient = null;
181049
+ this.wsLifecycle = null;
181050
+ }
180882
181051
  resolveSymbol(symbol5) {
180883
181052
  const { dex, name } = parseDex(symbol5);
180884
181053
  if (dex !== null) {
@@ -180937,52 +181106,17 @@ class HyperliquidClient {
180937
181106
  async getPositions(address) {
180938
181107
  const user = address ?? this.defaultAddress;
180939
181108
  const data = await this.info("clearinghouseState", { user });
180940
- const positions = [];
180941
- for (const ap of data.assetPositions ?? []) {
180942
- const pos = ap.position;
180943
- const szi = parseFloat(pos.szi);
180944
- if (szi === 0)
180945
- continue;
180946
- const entryPx = parseFloat(pos.entryPx);
180947
- const markPx = pos.positionValue ? Math.abs(parseFloat(pos.positionValue) / szi) : entryPx;
180948
- const upnl = parseFloat(pos.unrealizedPnl);
180949
- const margin = parseFloat(pos.marginUsed);
180950
- positions.push({
180951
- symbol: pos.coin,
180952
- side: szi > 0 ? "long" : "short",
180953
- size: Math.abs(szi),
180954
- entryPrice: entryPx,
180955
- markPrice: markPx,
180956
- liquidationPrice: pos.liquidationPx && pos.liquidationPx !== "0.0" && parseFloat(pos.liquidationPx) > 0 ? parseFloat(pos.liquidationPx) : null,
180957
- unrealizedPnl: upnl,
180958
- unrealizedPnlPct: parseFloat(pos.returnOnEquity ?? "0") * 100,
180959
- leverage: parseFloat(pos.leverage?.value ?? "1"),
180960
- marginMode: pos.leverage?.type === "isolated" ? "isolated" : "cross",
180961
- margin
180962
- });
180963
- }
180964
- return positions;
181109
+ return mapPositionsFromRaw(data);
180965
181110
  }
180966
181111
  async getOpenOrders(address) {
180967
181112
  const user = address ?? this.defaultAddress;
180968
181113
  const data = await this.info("frontendOpenOrders", { user });
180969
- return data.map((o10) => ({
180970
- orderId: String(o10.oid),
180971
- symbol: o10.coin,
180972
- side: o10.side === "B" ? "buy" : "sell",
180973
- orderType: o10.orderType ?? "Limit",
180974
- price: o10.limitPx ? parseFloat(o10.limitPx) : null,
180975
- triggerPrice: o10.triggerPx && o10.triggerPx !== "0.0" ? parseFloat(o10.triggerPx) : null,
180976
- size: parseFloat(o10.sz),
180977
- filled: parseFloat(o10.origSz ?? o10.sz) - parseFloat(o10.sz),
180978
- reduceOnly: o10.reduceOnly ?? false,
180979
- timestamp: o10.timestamp ?? Date.now()
180980
- }));
181114
+ return data.map(mapOpenOrderFromRaw);
180981
181115
  }
180982
181116
  async getFills(address, limit2 = 20) {
180983
181117
  const user = address ?? this.defaultAddress;
180984
181118
  const data = await this.info("userFills", { user });
180985
- return data.slice(0, limit2).map((f2) => mapFill(f2));
181119
+ return data.slice(0, limit2).map(mapFill);
180986
181120
  }
180987
181121
  async getFillsByTime(address, startTime, endTime) {
180988
181122
  const user = address ?? this.defaultAddress;
@@ -180990,7 +181124,7 @@ class HyperliquidClient {
180990
181124
  if (endTime !== undefined)
180991
181125
  params.endTime = endTime;
180992
181126
  const data = await this.info("userFillsByTime", params);
180993
- return data.map((f2) => mapFill(f2));
181127
+ return data.map(mapFill);
180994
181128
  }
180995
181129
  async getHistoricalOrders(address, startTime) {
180996
181130
  const user = address ?? this.defaultAddress;
@@ -181018,7 +181152,7 @@ class HyperliquidClient {
181018
181152
  async getAllTickers() {
181019
181153
  await this.ensureMeta();
181020
181154
  const tickers = [];
181021
- const nativeData = await this.info("metaAndAssetCtxs");
181155
+ const nativeData = await this.info("metaAndAssetCtxs", {}, { cache: true });
181022
181156
  const nativeCtxs = nativeData[1] ?? [];
181023
181157
  const responseNativeUniverse = nativeData[0]?.universe ?? [];
181024
181158
  const nativeNames = responseNativeUniverse.length > 0 ? responseNativeUniverse.map((a) => a.name) : this.dexUniverses.get("") ?? [];
@@ -181029,7 +181163,7 @@ class HyperliquidClient {
181029
181163
  tickers.push(ctxToTicker(nativeCtxs[i], name));
181030
181164
  }
181031
181165
  const dexes = await this.listPerpDexes().catch(() => []);
181032
- const dexCtxResults = await Promise.allSettled(dexes.map((d2) => this.info("metaAndAssetCtxs", { dex: d2.name })));
181166
+ const dexCtxResults = await runWithConcurrency(dexes, 4, (d2) => this.info("metaAndAssetCtxs", { dex: d2.name }, { cache: true }));
181033
181167
  for (let i = 0;i < dexCtxResults.length; i++) {
181034
181168
  const r2 = dexCtxResults[i];
181035
181169
  if (r2.status !== "fulfilled") {
@@ -181077,7 +181211,8 @@ class HyperliquidClient {
181077
181211
  "1w": 604800000
181078
181212
  };
181079
181213
  const ms2 = intervalMs[interval] ?? 3600000;
181080
- const endTime = Date.now();
181214
+ const QUANTISE_MS = 3000;
181215
+ const endTime = Math.floor(Date.now() / QUANTISE_MS) * QUANTISE_MS;
181081
181216
  const startTime = endTime - limit2 * ms2;
181082
181217
  let timer;
181083
181218
  try {
@@ -181085,7 +181220,7 @@ class HyperliquidClient {
181085
181220
  timer = setTimeout(() => reject(new Error("Hyperliquid klines timeout (10s)")), 1e4);
181086
181221
  });
181087
181222
  const data = await Promise.race([
181088
- this.info("candleSnapshot", { req: { coin: resolved, interval, startTime, endTime } }),
181223
+ this.info("candleSnapshot", { req: { coin: resolved, interval, startTime, endTime } }, { cache: true }),
181089
181224
  timeout
181090
181225
  ]);
181091
181226
  return (data ?? []).slice(-limit2).map((c2) => ({
@@ -181902,6 +182037,21 @@ class PaperTradingClient {
181902
182037
  getMaxLeverage(symbol5) {
181903
182038
  return this.marketClient.getMaxLeverage(symbol5);
181904
182039
  }
182040
+ getAllAssetNames() {
182041
+ return this.marketClient.getAllAssetNames();
182042
+ }
182043
+ isKnownSymbol(symbol5) {
182044
+ return this.marketClient.isKnownSymbol(symbol5);
182045
+ }
182046
+ getDexUniverses() {
182047
+ return this.marketClient.getDexUniverses();
182048
+ }
182049
+ subscribeAllDexsAssetCtxs(_listener) {
182050
+ return Promise.resolve(NOOP_SUB);
182051
+ }
182052
+ closeWs() {
182053
+ return Promise.resolve();
182054
+ }
181905
182055
  getBalance(_address) {
181906
182056
  return this.engine.getBalance();
181907
182057
  }
@@ -181948,8 +182098,10 @@ class PaperTradingClient {
181948
182098
  this.engine.reset(newBalance);
181949
182099
  }
181950
182100
  }
182101
+ var NOOP_SUB;
181951
182102
  var init_client7 = __esm(() => {
181952
182103
  init_engine();
182104
+ NOOP_SUB = { unsubscribe: async () => {} };
181953
182105
  });
181954
182106
 
181955
182107
  // src/services/intel.ts
@@ -182304,8 +182456,8 @@ class NotificationsService {
182304
182456
  // src/services/price-cache.ts
182305
182457
  class PriceCache {
182306
182458
  prices = new Map;
182307
- set(symbol5, price) {
182308
- this.prices.set(symbol5, { price, timestamp: Date.now() });
182459
+ set(symbol5, price, prevDayPrice) {
182460
+ this.prices.set(symbol5, { price, timestamp: Date.now(), prevDayPrice });
182309
182461
  }
182310
182462
  get(symbol5, maxAgeMs = 30000) {
182311
182463
  const entry = this.prices.get(symbol5);
@@ -187422,11 +187574,8 @@ function createAdvancedTradingTools(hl2, watchlist, alerts, priceCache) {
187422
187574
  async execute(_toolCallId, params) {
187423
187575
  try {
187424
187576
  const upper2 = params.symbol.toUpperCase();
187425
- try {
187426
- await hl2.getTicker(upper2);
187427
- } catch {
187577
+ if (!hl2.isKnownSymbol(upper2))
187428
187578
  return errorResult(`Symbol ${upper2} not found on Hyperliquid`);
187429
- }
187430
187579
  const item = await watchlist.add(upper2, params.notes);
187431
187580
  return textResult(`Added ${item.symbol} to watchlist.${item.notes ? ` Notes: ${item.notes}` : ""}`);
187432
187581
  } catch (e2) {
@@ -187493,11 +187642,8 @@ function createAdvancedTradingTools(hl2, watchlist, alerts, priceCache) {
187493
187642
  try {
187494
187643
  const cond = params.condition;
187495
187644
  const upper2 = params.symbol.toUpperCase();
187496
- try {
187497
- await hl2.getTicker(upper2);
187498
- } catch {
187645
+ if (!hl2.isKnownSymbol(upper2))
187499
187646
  return errorResult(`Symbol ${upper2} not found on Hyperliquid.`);
187500
- }
187501
187647
  const lookup2 = await getCurrentPrice(hl2, priceCache, upper2);
187502
187648
  if (lookup2.price !== undefined) {
187503
187649
  const past = cond === "above" ? lookup2.price >= params.price : lookup2.price <= params.price;
@@ -189072,6 +189218,42 @@ var init_judge2 = __esm(() => {
189072
189218
  });
189073
189219
  });
189074
189220
 
189221
+ // src/observer/pnl-drift.ts
189222
+ function decidePnlDrift(input) {
189223
+ const { state, currentPnl, lastUserActivityMs, nowMs } = input;
189224
+ if (state.lastSnapshotPnl === null) {
189225
+ return { fire: false, nextSnapshotPnl: currentPnl, reason: "seed_baseline" };
189226
+ }
189227
+ const base = Math.max(Math.abs(state.lastSnapshotPnl), PNL_DRIFT_MIN_BASE);
189228
+ const deltaPct = (currentPnl - state.lastSnapshotPnl) / base;
189229
+ const absDeltaPct = Math.abs(deltaPct);
189230
+ if (absDeltaPct < PNL_DRIFT_THRESHOLD_PCT) {
189231
+ return { fire: false, nextSnapshotPnl: state.lastSnapshotPnl, reason: "below_threshold" };
189232
+ }
189233
+ const idleMs = lastUserActivityMs === null ? SENTINEL_MAX_IDLE_MS : nowMs - lastUserActivityMs;
189234
+ if (idleMs < PNL_DRIFT_IDLE_GATE_MS) {
189235
+ return { fire: false, nextSnapshotPnl: state.lastSnapshotPnl, reason: "user_active" };
189236
+ }
189237
+ if (state.lastSentAtMs !== null && nowMs - state.lastSentAtMs < PNL_DRIFT_COOLDOWN_MS) {
189238
+ return { fire: false, nextSnapshotPnl: state.lastSnapshotPnl, reason: "cooldown" };
189239
+ }
189240
+ const event2 = {
189241
+ type: "portfolio_pnl_drift",
189242
+ detectedAt: nowMs,
189243
+ fromPnl: state.lastSnapshotPnl,
189244
+ toPnl: currentPnl,
189245
+ deltaPct,
189246
+ idleMs
189247
+ };
189248
+ return { fire: true, event: event2, nextSnapshotPnl: currentPnl };
189249
+ }
189250
+ var PNL_DRIFT_THRESHOLD_PCT = 0.15, PNL_DRIFT_IDLE_GATE_MS, PNL_DRIFT_COOLDOWN_MS, PNL_DRIFT_MIN_BASE = 10, SENTINEL_MAX_IDLE_MS;
189251
+ var init_pnl_drift = __esm(() => {
189252
+ PNL_DRIFT_IDLE_GATE_MS = 2 * 60 * 60 * 1000;
189253
+ PNL_DRIFT_COOLDOWN_MS = 6 * 60 * 60 * 1000;
189254
+ SENTINEL_MAX_IDLE_MS = 7 * 24 * 60 * 60 * 1000;
189255
+ });
189256
+
189075
189257
  // src/channels/base.ts
189076
189258
  class BaseChannel {
189077
189259
  config;
@@ -195305,6 +195487,7 @@ class ObserverLoop {
195305
195487
  store;
195306
195488
  cachedRest = null;
195307
195489
  lastProactiveAtMs;
195490
+ pnlDriftState = { lastSnapshotPnl: null, lastSentAtMs: null };
195308
195491
  constructor(deps) {
195309
195492
  this.deps = deps;
195310
195493
  this.store = new ObserverStateStore(deps.db);
@@ -195356,6 +195539,26 @@ class ObserverLoop {
195356
195539
  this.deps.alertRules.markFired(id, Math.floor(nowMs / 1000));
195357
195540
  }
195358
195541
  const gatedEvents = filterPnlSnapshots(diff.events, prior.positions, nowMs);
195542
+ if (rest4.positions.length === 0) {
195543
+ this.pnlDriftState = { lastSnapshotPnl: null, lastSentAtMs: null };
195544
+ } else {
195545
+ const totalUnrealizedPnl = rest4.positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
195546
+ const session = this.deps.sessionManager.getOrCreate(MAIN_SESSION_KEY);
195547
+ const lastUserActivityMs = session.lastActiveAt?.getTime() ?? null;
195548
+ const driftDecision = decidePnlDrift({
195549
+ state: this.pnlDriftState,
195550
+ currentPnl: totalUnrealizedPnl,
195551
+ lastUserActivityMs,
195552
+ nowMs
195553
+ });
195554
+ if (driftDecision.fire) {
195555
+ gatedEvents.push(driftDecision.event);
195556
+ }
195557
+ this.pnlDriftState = {
195558
+ lastSnapshotPnl: driftDecision.nextSnapshotPnl,
195559
+ lastSentAtMs: driftDecision.fire ? nowMs : this.pnlDriftState.lastSentAtMs
195560
+ };
195561
+ }
195359
195562
  const nextSnapshot = {
195360
195563
  positions: diff.nextPositions,
195361
195564
  lastFillTimestamp: rest4.latestFillTimestamp,
@@ -195522,12 +195725,14 @@ class ObserverLoop {
195522
195725
  const m6 = msg;
195523
195726
  if (m6.role !== "user" && m6.role !== "assistant")
195524
195727
  continue;
195525
- if (!Array.isArray(m6.content))
195526
- continue;
195527
195728
  let text = "";
195528
- for (const block of m6.content) {
195529
- if (block?.type === "text" && typeof block.text === "string") {
195530
- text += block.text;
195729
+ if (typeof m6.content === "string") {
195730
+ text = m6.content;
195731
+ } else if (Array.isArray(m6.content)) {
195732
+ for (const block of m6.content) {
195733
+ if (block?.type === "text" && typeof block.text === "string") {
195734
+ text += block.text;
195735
+ }
195531
195736
  }
195532
195737
  }
195533
195738
  if (text.trim().length === 0)
@@ -195605,6 +195810,7 @@ var RECENT_CHAT_MAX = 20, MIN_PRICE_PCT_DELTA = 0.5, MIN_COOLDOWN_MS;
195605
195810
  var init_loop = __esm(() => {
195606
195811
  init_diff();
195607
195812
  init_judge2();
195813
+ init_pnl_drift();
195608
195814
  init_channels();
195609
195815
  init_types5();
195610
195816
  MIN_COOLDOWN_MS = 60 * 60 * 1000;
@@ -213083,34 +213289,19 @@ function registerTradingMethods(register, deps) {
213083
213289
  return { fills: filtered, window: { startTime, endTime }, capped };
213084
213290
  });
213085
213291
  register("trading.tokens.list", async () => {
213086
- try {
213087
- const tickers = await deps.tradingClient.getAllTickers();
213088
- const prices = {};
213089
- const prevDayPrices = {};
213090
- const maxLeverages = {};
213091
- const tokens = [];
213092
- for (const t10 of tickers) {
213093
- tokens.push(t10.symbol);
213094
- prices[t10.symbol] = t10.markPrice;
213095
- if (t10.prevDayPrice > 0)
213096
- prevDayPrices[t10.symbol] = t10.prevDayPrice;
213097
- const lev = deps.tradingClient.getMaxLeverage(t10.symbol);
213098
- if (typeof lev === "number" && lev > 0)
213099
- maxLeverages[t10.symbol] = lev;
213100
- }
213101
- tokens.sort();
213102
- return { tokens, prices, prevDayPrices, maxLeverages };
213103
- } catch {
213104
- return { tokens: [], prices: {}, prevDayPrices: {}, maxLeverages: {} };
213105
- }
213292
+ return deps.tokensSnapshot.build();
213106
213293
  });
213107
213294
  register("trading.price", async (_ctx, payload) => {
213108
213295
  const params = typeof payload === "object" && payload !== null ? payload : {};
213109
213296
  const symbol5 = typeof params.symbol === "string" ? params.symbol : undefined;
213110
213297
  if (!symbol5)
213111
213298
  return { price: null, symbol: null };
213299
+ const resolved = deps.tradingClient.resolveSymbol(symbol5);
213300
+ const entry = deps.priceCache.get(resolved, 30000);
213301
+ if (entry)
213302
+ return { symbol: resolved, price: entry.price };
213112
213303
  try {
213113
- const ticker = await deps.tradingClient.getTicker(symbol5);
213304
+ const ticker = await deps.tradingClient.getTicker(resolved);
213114
213305
  return { symbol: ticker.symbol, price: ticker.markPrice };
213115
213306
  } catch {
213116
213307
  return { price: null, symbol: symbol5 };
@@ -213129,9 +213320,7 @@ function registerTradingMethods(register, deps) {
213129
213320
  if (!symbol5)
213130
213321
  return { error: "symbol required" };
213131
213322
  const resolved = deps.tradingClient.resolveSymbol(symbol5);
213132
- try {
213133
- await deps.tradingClient.getTicker(resolved);
213134
- } catch {
213323
+ if (!deps.tradingClient.isKnownSymbol(resolved)) {
213135
213324
  return { error: `Symbol ${resolved} not found on Hyperliquid` };
213136
213325
  }
213137
213326
  try {
@@ -213840,7 +214029,7 @@ class CompositePriceFeed {
213840
214029
  this.log.info({ sources: names }, "composite price feed starting");
213841
214030
  await Promise.all(this.sources.map(async (source2) => {
213842
214031
  try {
213843
- await source2.start((symbol5, price) => this.handleTick(source2, symbol5, price));
214032
+ await source2.start((symbol5, price, prevDayPrice) => this.handleTick(source2, symbol5, price, prevDayPrice));
213844
214033
  } catch (err) {
213845
214034
  this.log.warn({ err, source: source2.name }, "source failed to start");
213846
214035
  }
@@ -213871,7 +214060,7 @@ class CompositePriceFeed {
213871
214060
  this.healthySince.clear();
213872
214061
  this.onPrice = null;
213873
214062
  }
213874
- handleTick(source2, symbol5, price) {
214063
+ handleTick(source2, symbol5, price, prevDayPrice) {
213875
214064
  if (!this.onPrice)
213876
214065
  return;
213877
214066
  if (this.currentPrimary === null) {
@@ -213881,7 +214070,7 @@ class CompositePriceFeed {
213881
214070
  }
213882
214071
  if (source2.name !== this.currentPrimary)
213883
214072
  return;
213884
- this.onPrice(symbol5, price);
214073
+ this.onPrice(symbol5, price, prevDayPrice);
213885
214074
  }
213886
214075
  isHealthy(source2) {
213887
214076
  const last = source2.getLastTickAt();
@@ -213960,44 +214149,6 @@ var init_composite3 = __esm(() => {
213960
214149
  init_types6();
213961
214150
  });
213962
214151
 
213963
- // src/services/price-feed/sources/bun-ws-compat.ts
213964
- function applyShim() {
213965
- const proto = globalThis.WebSocket?.prototype;
213966
- if (!proto || proto.__ghostBlobSetterPatched)
213967
- return proto?.__ghostBlobSetterPatched === true;
213968
- const desc = Object.getOwnPropertyDescriptor(proto, "binaryType");
213969
- if (!desc?.set)
213970
- return false;
213971
- const originalSet = desc.set;
213972
- Object.defineProperty(proto, "binaryType", {
213973
- configurable: true,
213974
- enumerable: desc.enumerable ?? false,
213975
- get: desc.get,
213976
- set(value2) {
213977
- if (value2 === "blob") {
213978
- const g7 = globalThis;
213979
- if (!g7.__ghostBlobWarned) {
213980
- g7.__ghostBlobWarned = true;
213981
- console.warn("[ghost] WebSocket.binaryType='blob' silently dropped (Bun compat shim \u2014 see src/services/price-feed/sources/bun-ws-compat.ts)");
213982
- }
213983
- return;
213984
- }
213985
- originalSet.call(this, value2);
213986
- }
213987
- });
213988
- Object.defineProperty(proto, "__ghostBlobSetterPatched", {
213989
- value: true,
213990
- writable: false,
213991
- enumerable: false,
213992
- configurable: false
213993
- });
213994
- return true;
213995
- }
213996
- var BUN_WS_COMPAT_APPLIED;
213997
- var init_bun_ws_compat = __esm(() => {
213998
- BUN_WS_COMPAT_APPLIED = applyShim();
213999
- });
214000
-
214001
214152
  // src/services/price-feed/sources/hyperliquid.ts
214002
214153
  class HyperliquidSource {
214003
214154
  name = "hyperliquid";
@@ -214011,13 +214162,10 @@ class HyperliquidSource {
214011
214162
  healthCheckIntervalMs;
214012
214163
  onTick = null;
214013
214164
  stopped = true;
214014
- transport = null;
214015
- client = null;
214016
- subscription = null;
214165
+ wsSubscription = null;
214017
214166
  wsRetryTimer = null;
214018
214167
  wsRetryCount = 0;
214019
214168
  lastWsTickAt = 0;
214020
- universe = [];
214021
214169
  restTimer = null;
214022
214170
  restPolling = false;
214023
214171
  lastRestTickAt = 0;
@@ -214052,12 +214200,34 @@ class HyperliquidSource {
214052
214200
  restIntervalMs: this.restIntervalMs
214053
214201
  }, "hyperliquid source starting (WS primary, REST dormant)");
214054
214202
  await this.connectWs();
214203
+ if (this.stopped)
214204
+ return;
214205
+ await this.hydrateFromRest();
214055
214206
  if (this.stopped)
214056
214207
  return;
214057
214208
  this.healthTimer = setInterval(() => {
214058
214209
  this.reconcileTransports();
214059
214210
  }, this.healthCheckIntervalMs);
214060
214211
  }
214212
+ async hydrateFromRest() {
214213
+ try {
214214
+ const tickers = await this.tradingClient.getAllTickers();
214215
+ if (this.stopped)
214216
+ return;
214217
+ const callback = this.onTick;
214218
+ if (!callback)
214219
+ return;
214220
+ for (const t10 of tickers) {
214221
+ if (!Number.isFinite(t10.markPrice))
214222
+ continue;
214223
+ const prev = Number.isFinite(t10.prevDayPrice) && t10.prevDayPrice > 0 ? t10.prevDayPrice : undefined;
214224
+ callback(t10.symbol, t10.markPrice, prev);
214225
+ }
214226
+ this.log.info({ count: tickers.length }, "hyperliquid source: REST hydration complete");
214227
+ } catch (err) {
214228
+ this.log.warn({ err }, "hyperliquid source: REST hydration failed; relying on WS");
214229
+ }
214230
+ }
214061
214231
  async stop() {
214062
214232
  if (this.stopped)
214063
214233
  return;
@@ -214077,89 +214247,53 @@ class HyperliquidSource {
214077
214247
  async connectWs() {
214078
214248
  if (this.stopped)
214079
214249
  return;
214080
- let transport = null;
214081
- let client4 = null;
214082
- let subscription = null;
214083
214250
  try {
214084
- transport = new WebSocketTransport({ isTestnet: this.testnet });
214085
- client4 = new SubscriptionClient({ transport });
214086
- await this.refreshUniverse();
214087
- subscription = await client4.assetCtxs((event2) => this.handleAssetCtxsEvent(event2));
214251
+ await this.tradingClient.ensureMeta();
214252
+ if (this.stopped)
214253
+ return;
214254
+ const sub = await this.tradingClient.subscribeAllDexsAssetCtxs((event2) => this.handleAllDexsAssetCtxsEvent(event2));
214088
214255
  if (this.stopped) {
214089
214256
  try {
214090
- await subscription.unsubscribe();
214091
- } catch {}
214092
- try {
214093
- await transport.close();
214257
+ await sub.unsubscribe();
214094
214258
  } catch {}
214095
214259
  return;
214096
214260
  }
214097
- this.transport = transport;
214098
- this.client = client4;
214099
- this.subscription = subscription;
214261
+ this.wsSubscription = sub;
214100
214262
  this.wsRetryCount = 0;
214101
- this.log.info("hyperliquid source: WS connected");
214102
- this.subscription.failureSignal.addEventListener("abort", () => {
214103
- if (this.stopped)
214104
- return;
214105
- this.log.warn("hyperliquid source: WS subscription failed, scheduling retry");
214106
- this.scheduleWsRetry();
214107
- });
214108
- this.transport.socket.addEventListener("terminate", (ev2) => {
214109
- if (this.stopped)
214110
- return;
214111
- this.log.warn({ reason: ev2.detail?.code, cause: serializeError(ev2.detail?.cause) }, "hyperliquid source: WS transport terminated, scheduling retry");
214112
- this.scheduleWsRetry();
214113
- });
214114
- this.transport.socket.addEventListener("error", (ev2) => {
214115
- if (this.stopped)
214116
- return;
214117
- this.log.warn({ event: serializeWsErrorEvent(ev2) }, "hyperliquid source: WS transport error");
214118
- });
214263
+ this.log.info("hyperliquid source: WS connected (allDexsAssetCtxs)");
214119
214264
  } catch (err) {
214120
214265
  this.log.warn({ err }, "hyperliquid source: WS failed to connect");
214121
- if (subscription) {
214122
- try {
214123
- await subscription.unsubscribe();
214124
- } catch {}
214125
- }
214126
- if (transport) {
214127
- try {
214128
- await transport.close();
214129
- } catch {}
214130
- }
214131
214266
  await this.cleanupWs();
214132
214267
  this.scheduleWsRetry();
214133
214268
  }
214134
214269
  }
214135
- async refreshUniverse() {
214136
- try {
214137
- const tickers = await this.tradingClient.getAllTickers();
214138
- this.universe = tickers.map((t10) => t10.symbol);
214139
- } catch (err) {
214140
- this.log.warn({ err }, "hyperliquid source: universe refresh failed");
214141
- }
214142
- }
214143
- handleAssetCtxsEvent(event2) {
214270
+ handleAllDexsAssetCtxsEvent(event2) {
214144
214271
  if (this.stopped)
214145
214272
  return;
214146
214273
  const callback = this.onTick;
214147
214274
  if (!callback)
214148
214275
  return;
214276
+ const dexUniverses = this.tradingClient.getDexUniverses();
214149
214277
  const now = Date.now();
214150
214278
  let anyEmitted = false;
214151
- for (let i = 0;i < event2.ctxs.length; i++) {
214152
- const symbol5 = this.universe[i];
214153
- if (!symbol5)
214154
- continue;
214155
- const raw = event2.ctxs[i]?.markPx;
214156
- if (raw === null || raw === undefined)
214157
- continue;
214158
- const mark = typeof raw === "string" ? parseFloat(raw) : Number(raw);
214159
- if (!Number.isFinite(mark))
214160
- continue;
214161
- callback(symbol5, mark);
214162
- anyEmitted = true;
214279
+ for (const [dex, ctxs] of event2.ctxs) {
214280
+ const universe = dexUniverses.get(dex) ?? [];
214281
+ for (let i = 0;i < ctxs.length; i++) {
214282
+ const symbol5 = universe[i];
214283
+ if (!symbol5)
214284
+ continue;
214285
+ const ctx = ctxs[i];
214286
+ const raw = ctx?.markPx;
214287
+ if (raw === null || raw === undefined)
214288
+ continue;
214289
+ const mark = typeof raw === "string" ? parseFloat(raw) : Number(raw);
214290
+ if (!Number.isFinite(mark))
214291
+ continue;
214292
+ const prevRaw = ctx?.prevDayPx;
214293
+ const prevDay = prevRaw != null ? typeof prevRaw === "string" ? parseFloat(prevRaw) : Number(prevRaw) : undefined;
214294
+ callback(symbol5, mark, Number.isFinite(prevDay) ? prevDay : undefined);
214295
+ anyEmitted = true;
214296
+ }
214163
214297
  }
214164
214298
  if (anyEmitted)
214165
214299
  this.lastWsTickAt = now;
@@ -214179,19 +214313,12 @@ class HyperliquidSource {
214179
214313
  }, delay4);
214180
214314
  }
214181
214315
  async cleanupWs() {
214182
- if (this.subscription) {
214183
- try {
214184
- await this.subscription.unsubscribe();
214185
- } catch {}
214186
- this.subscription = null;
214187
- }
214188
- if (this.transport) {
214316
+ if (this.wsSubscription) {
214189
214317
  try {
214190
- await this.transport.close();
214318
+ await this.wsSubscription.unsubscribe();
214191
214319
  } catch {}
214192
- this.transport = null;
214320
+ this.wsSubscription = null;
214193
214321
  }
214194
- this.client = null;
214195
214322
  }
214196
214323
  activateRest() {
214197
214324
  if (this.stopped)
@@ -214229,7 +214356,8 @@ class HyperliquidSource {
214229
214356
  return;
214230
214357
  if (!Number.isFinite(t10.markPrice))
214231
214358
  continue;
214232
- callback(t10.symbol, t10.markPrice);
214359
+ const prevDay = Number.isFinite(t10.prevDayPrice) && t10.prevDayPrice > 0 ? t10.prevDayPrice : undefined;
214360
+ callback(t10.symbol, t10.markPrice, prevDay);
214233
214361
  anyEmitted = true;
214234
214362
  }
214235
214363
  if (anyEmitted)
@@ -214274,7 +214402,7 @@ class HyperliquidSource {
214274
214402
  this.deactivateRest();
214275
214403
  }
214276
214404
  maybeForceWsReconnect(wsAge) {
214277
- if (!this.subscription)
214405
+ if (!this.wsSubscription)
214278
214406
  return;
214279
214407
  if (this.wsRetryTimer)
214280
214408
  return;
@@ -214288,27 +214416,7 @@ class HyperliquidSource {
214288
214416
  });
214289
214417
  }
214290
214418
  }
214291
- function serializeWsErrorEvent(ev2) {
214292
- const e2 = ev2;
214293
- const underlying = e2.error;
214294
- return {
214295
- type: ev2.type,
214296
- message: typeof e2.message === "string" ? e2.message : undefined,
214297
- error: underlying instanceof Error ? { name: underlying.name, message: underlying.message } : underlying !== undefined ? String(underlying) : undefined
214298
- };
214299
- }
214300
- function serializeError(cause) {
214301
- if (cause === undefined || cause === null)
214302
- return;
214303
- if (cause instanceof Error)
214304
- return `${cause.name}: ${cause.message}`;
214305
- return String(cause);
214306
- }
214307
214419
  var DEFAULT_REST_INTERVAL_MS = 5000, DEFAULT_WS_STALE_MS = 1e4, DEFAULT_WS_STABILITY_MS = 5000, DEFAULT_HEALTH_CHECK_INTERVAL_MS = 1000;
214308
- var init_hyperliquid = __esm(() => {
214309
- init_bun_ws_compat();
214310
- init_mod6();
214311
- });
214312
214420
 
214313
214421
  // src/services/price-feed/symbol-mapping.ts
214314
214422
  function mapBinanceSymbol(binanceSymbol) {
@@ -214581,7 +214689,7 @@ class BinanceSource {
214581
214689
  ws.addEventListener("error", (ev2) => {
214582
214690
  if (this.stopped)
214583
214691
  return;
214584
- this.log.warn({ event: serializeWsErrorEvent2(ev2) }, "binance source: WS error");
214692
+ this.log.warn({ event: serializeWsErrorEvent(ev2) }, "binance source: WS error");
214585
214693
  });
214586
214694
  } catch (err) {
214587
214695
  this.log.warn({ err }, "binance source: WS failed to open");
@@ -214612,7 +214720,7 @@ class BinanceSource {
214612
214720
  const mark = entry?.p;
214613
214721
  if (typeof sym !== "string" || typeof mark !== "string")
214614
214722
  continue;
214615
- const emitted = this.emitMapped(sym, mark, callback);
214723
+ const emitted = this.emitMapped(sym, mark, entry.o, callback);
214616
214724
  if (emitted)
214617
214725
  anyEmitted = true;
214618
214726
  }
@@ -214685,7 +214793,7 @@ class BinanceSource {
214685
214793
  const mark = row?.markPrice;
214686
214794
  if (typeof sym !== "string" || typeof mark !== "string")
214687
214795
  continue;
214688
- const emitted = this.emitMapped(sym, mark, callback);
214796
+ const emitted = this.emitMapped(sym, mark, row.openPrice, callback);
214689
214797
  if (emitted)
214690
214798
  anyEmitted = true;
214691
214799
  }
@@ -214702,7 +214810,7 @@ class BinanceSource {
214702
214810
  }
214703
214811
  }
214704
214812
  }
214705
- emitMapped(binanceSymbol, priceStr, callback) {
214813
+ emitMapped(binanceSymbol, priceStr, prevDayPriceStr, callback) {
214706
214814
  if (!binanceSymbol.endsWith("USDT"))
214707
214815
  return false;
214708
214816
  const base = binanceSymbol.slice(0, -4);
@@ -214719,7 +214827,14 @@ class BinanceSource {
214719
214827
  if (!Number.isFinite(binancePrice))
214720
214828
  return false;
214721
214829
  const hlPrice = binancePrice * mapping.multiplier;
214722
- callback(mapping.hlSymbol, hlPrice);
214830
+ let prevDayPrice;
214831
+ if (prevDayPriceStr !== undefined) {
214832
+ const rawPrev = Number.parseFloat(prevDayPriceStr);
214833
+ if (Number.isFinite(rawPrev) && rawPrev > 0) {
214834
+ prevDayPrice = rawPrev * mapping.multiplier;
214835
+ }
214836
+ }
214837
+ callback(mapping.hlSymbol, hlPrice, prevDayPrice);
214723
214838
  return true;
214724
214839
  }
214725
214840
  reconcileTransports() {
@@ -214751,7 +214866,7 @@ class BinanceSource {
214751
214866
  this.deactivateRest();
214752
214867
  }
214753
214868
  }
214754
- function serializeWsErrorEvent2(ev2) {
214869
+ function serializeWsErrorEvent(ev2) {
214755
214870
  const e2 = ev2;
214756
214871
  const underlying = e2.error;
214757
214872
  return {
@@ -214778,6 +214893,35 @@ var init_binance2 = __esm(() => {
214778
214893
  LEVERAGED_SUFFIX_RE = /(?:UP|DOWN|BULL|BEAR)$/;
214779
214894
  });
214780
214895
 
214896
+ // src/services/tokens-snapshot.ts
214897
+ class TokensSnapshotService {
214898
+ client;
214899
+ priceCache;
214900
+ constructor(client4, priceCache) {
214901
+ this.client = client4;
214902
+ this.priceCache = priceCache;
214903
+ }
214904
+ build() {
214905
+ const tokens = this.client.getAllAssetNames();
214906
+ const prices = {};
214907
+ const prevDayPrices = {};
214908
+ const maxLeverages = {};
214909
+ for (const symbol5 of tokens) {
214910
+ const entry = this.priceCache.get(symbol5, 30000);
214911
+ if (entry) {
214912
+ prices[symbol5] = entry.price;
214913
+ if (entry.prevDayPrice !== undefined)
214914
+ prevDayPrices[symbol5] = entry.prevDayPrice;
214915
+ }
214916
+ const lev = this.client.getMaxLeverage(symbol5);
214917
+ if (typeof lev === "number" && lev > 0)
214918
+ maxLeverages[symbol5] = lev;
214919
+ }
214920
+ tokens.sort();
214921
+ return { tokens, prices, prevDayPrices, maxLeverages };
214922
+ }
214923
+ }
214924
+
214781
214925
  // node_modules/viem/_esm/utils/address/isAddressEqual.js
214782
214926
  function isAddressEqual(a, b5) {
214783
214927
  if (!isAddress(a, { strict: false }))
@@ -214901,7 +215045,8 @@ function createGateway(gatewayConfig, deps) {
214901
215045
  registerToolsMethods(registry4.register.bind(registry4), { tools: deps.tools });
214902
215046
  registerSessionsMethods(registry4.register.bind(registry4), { sessionManager: deps.sessionManager });
214903
215047
  registerCronMethods(registry4.register.bind(registry4), { cronService: deps.cronService });
214904
- registerTradingMethods(registry4.register.bind(registry4), { tradingClient: deps.tradingClient, walletStore: deps.walletStore, alertRules: deps.alertRules, notifications: deps.notifications, newsService: deps.newsService, rssDiscovery: deps.rssDiscoveryService, tweetService: deps.tweetService, xFollowService: deps.xFollowService, preferenceStore: deps.preferenceStore, watchlist: deps.watchlistService, logger: deps.logger });
215048
+ const tokensSnapshot = new TokensSnapshotService(deps.tradingClient, deps.priceCache);
215049
+ registerTradingMethods(registry4.register.bind(registry4), { tradingClient: deps.tradingClient, walletStore: deps.walletStore, alertRules: deps.alertRules, notifications: deps.notifications, newsService: deps.newsService, rssDiscovery: deps.rssDiscoveryService, tweetService: deps.tweetService, xFollowService: deps.xFollowService, preferenceStore: deps.preferenceStore, watchlist: deps.watchlistService, logger: deps.logger, tokensSnapshot, priceCache: deps.priceCache });
214905
215050
  registerApprovalMethods(registry4.register.bind(registry4), { approvalManager: deps.approvalManager });
214906
215051
  registerToolApprovalMethods(registry4.register.bind(registry4), { approvalManager: deps.approvalManager });
214907
215052
  registerSkillsMethods(registry4.register.bind(registry4), { skillService: deps.skillService });
@@ -214930,10 +215075,6 @@ function createGateway(gatewayConfig, deps) {
214930
215075
  deps.eventBus.publish(TradingEvents.watchlistChanged({ action, symbol: symbol5 }));
214931
215076
  });
214932
215077
  const lastPrices = new Map;
214933
- let watchedSymbols = new Set(deps.watchlistService.list().map((i) => i.symbol));
214934
- deps.watchlistService.onChanged(() => {
214935
- watchedSymbols = new Set(deps.watchlistService.list().map((i) => i.symbol));
214936
- });
214937
215078
  const priceFeedConfig = deps.config.priceFeed;
214938
215079
  const priceFeedLogger = deps.logger.child({ module: "price-feed" });
214939
215080
  function buildPriceSources() {
@@ -214956,15 +215097,13 @@ function createGateway(gatewayConfig, deps) {
214956
215097
  staleThresholdMs: priceFeedConfig.staleThresholdMs,
214957
215098
  stabilityWindowMs: priceFeedConfig.stabilityWindowMs
214958
215099
  }, priceFeedLogger);
214959
- function broadcastPrice(symbol5, price) {
215100
+ function broadcastPrice(symbol5, price, prevDayPrice) {
215101
+ deps.priceCache.set(symbol5, price, prevDayPrice);
214960
215102
  const prev = lastPrices.get(symbol5);
214961
- if (prev === price)
215103
+ if (prev === price && prevDayPrice === undefined)
214962
215104
  return;
214963
215105
  lastPrices.set(symbol5, price);
214964
- deps.priceCache.set(symbol5, price);
214965
- if (watchedSymbols.has(symbol5)) {
214966
- deps.eventBus.publish(TradingEvents.priceUpdate({ symbol: symbol5, price }));
214967
- }
215106
+ deps.eventBus.publish(TradingEvents.priceUpdate({ symbol: symbol5, price, prevDayPrice }));
214968
215107
  }
214969
215108
  async function startPriceFeed() {
214970
215109
  if (!priceFeedConfig.enabled) {
@@ -214973,7 +215112,7 @@ function createGateway(gatewayConfig, deps) {
214973
215112
  }
214974
215113
  lastPrices.clear();
214975
215114
  try {
214976
- await compositeFeed.start((symbol5, price) => broadcastPrice(symbol5, price));
215115
+ await compositeFeed.start((symbol5, price, prevDayPrice) => broadcastPrice(symbol5, price, prevDayPrice));
214977
215116
  } catch (err) {
214978
215117
  priceFeedLogger.warn({ err }, "composite price feed failed to start");
214979
215118
  }
@@ -215202,7 +215341,6 @@ var init_server = __esm(() => {
215202
215341
  init_wallet_events();
215203
215342
  init_trading_events();
215204
215343
  init_composite3();
215205
- init_hyperliquid();
215206
215344
  init_binance2();
215207
215345
  init_accounts();
215208
215346
  init__esm();
@@ -215229,12 +215367,16 @@ function snapshotRecentUserMessages(sessionManager, limit2) {
215229
215367
  const out = [];
215230
215368
  for (const msg of session.messages) {
215231
215369
  const m6 = msg;
215232
- if (m6.role !== "user" || !Array.isArray(m6.content))
215370
+ if (m6.role !== "user")
215233
215371
  continue;
215234
215372
  let text = "";
215235
- for (const block of m6.content) {
215236
- if (block?.type === "text" && typeof block.text === "string") {
215237
- text += block.text;
215373
+ if (typeof m6.content === "string") {
215374
+ text = m6.content;
215375
+ } else if (Array.isArray(m6.content)) {
215376
+ for (const block of m6.content) {
215377
+ if (block?.type === "text" && typeof block.text === "string") {
215378
+ text += block.text;
215379
+ }
215238
215380
  }
215239
215381
  }
215240
215382
  const trimmed = text.trim();
@@ -216035,6 +216177,9 @@ async function startDaemon(options) {
216035
216177
  runner.start();
216036
216178
  runtime.xFollowService.onEnable(() => runner.kick("tweet-fetch"));
216037
216179
  await runtime.walletReady;
216180
+ await tradingClient.ensureMeta().catch((err) => {
216181
+ logger.warn({ err }, "ensureMeta on startup failed \u2014 will retry on first use");
216182
+ });
216038
216183
  app.listen({ port: config3.gateway.port, hostname: config3.gateway.host });
216039
216184
  printDaemonStartupBanner({
216040
216185
  runtime,