@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.
- package/README.md +4 -20
- package/dist/index.js +394 -249
- package/dist/package.json +1 -1
- package/dist/skills/builtin/event-judge/SKILL.md +1 -0
- package/dist/web/dist/assets/{Chart-BOyhyITw.js → Chart-BgUhYrVH.js} +1 -1
- package/dist/web/dist/assets/{Config-CQyc8yqV.js → Config-CMp84sNc.js} +1 -1
- package/dist/web/dist/assets/{Cost-BoAvnlDi.js → Cost-BgKdnj4i.js} +1 -1
- package/dist/web/dist/assets/{Cron-5mIZVsft.js → Cron-L4GEQwj0.js} +1 -1
- package/dist/web/dist/assets/{Dashboard-BRxSDtCD.js → Dashboard-D1UNPhTW.js} +1 -1
- package/dist/web/dist/assets/{Logs-BpXqElSx.js → Logs-DZkot-wQ.js} +1 -1
- package/dist/web/dist/assets/{Memory-CcgSgCqJ.js → Memory-DsQackdo.js} +1 -1
- package/dist/web/dist/assets/SVN-RethinkSans-Bold-BIisIh3U.otf +0 -0
- package/dist/web/dist/assets/SVN-RethinkSans-Italic-8Q-lLPN5.otf +0 -0
- package/dist/web/dist/assets/SVN-RethinkSans-Medium-yLh4RBRv.otf +0 -0
- package/dist/web/dist/assets/SVN-RethinkSans-Regular-CVuIF6ni.otf +0 -0
- package/dist/web/dist/assets/{Sessions-BEIP55fW.js → Sessions-uqRIDn57.js} +1 -1
- package/dist/web/dist/assets/{Skills-4s4LDpzh.js → Skills-B9WQPgSe.js} +1 -1
- package/dist/web/dist/assets/{Tools-C-NcyONi.js → Tools-Dyf8Zp-H.js} +1 -1
- package/dist/web/dist/assets/{activity-DJy9m_N9.js → activity-DRn1WSJo.js} +1 -1
- package/dist/web/dist/assets/{clock-CFVWqojO.js → clock-FMsaqykN.js} +1 -1
- package/dist/web/dist/assets/empty-notifications-C88Hm9lC.svg +62 -0
- package/dist/web/dist/assets/empty-token-search-aBFBwedz.svg +5 -0
- package/dist/web/dist/assets/{highlighted-body-OFNGDK62-y5VVuvTf.js → highlighted-body-OFNGDK62-Ck5_e4wj.js} +1 -1
- package/dist/web/dist/assets/index-CGNakXCn.css +1 -0
- package/dist/web/dist/assets/index-DNznoLXb.js +54 -0
- package/dist/web/dist/assets/{mermaid-GHXKKRXX-BvdvTI5r.js → mermaid-GHXKKRXX-jlxJo7RH.js} +3 -3
- package/dist/web/dist/assets/{refresh-cw-DlbQ5OE4.js → refresh-cw-CRQa2SMT.js} +1 -1
- package/dist/web/dist/assets/welcome-globe-analytics-sF4OWku9.svg +201 -0
- package/dist/web/dist/assets/welcome-globe-bars-COA7MJUP.svg +305 -0
- package/dist/web/dist/assets/welcome-news-card-BfM47ZZ3.svg +59 -0
- package/dist/web/dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/dist/assets/index-B1ED3P_d.css +0 -1
- package/dist/web/dist/assets/index-C6QRLmO2.js +0 -54
- package/dist/web/dist/assets/rethink-sans-latin-ext-CuoiHPIp.woff2 +0 -0
- package/dist/web/dist/assets/rethink-sans-latin-v3CgWhBT.woff2 +0 -0
- 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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195529
|
-
|
|
195530
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
214085
|
-
|
|
214086
|
-
|
|
214087
|
-
|
|
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
|
|
214091
|
-
} catch {}
|
|
214092
|
-
try {
|
|
214093
|
-
await transport.close();
|
|
214257
|
+
await sub.unsubscribe();
|
|
214094
214258
|
} catch {}
|
|
214095
214259
|
return;
|
|
214096
214260
|
}
|
|
214097
|
-
this.
|
|
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
|
-
|
|
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 (
|
|
214152
|
-
const
|
|
214153
|
-
|
|
214154
|
-
|
|
214155
|
-
|
|
214156
|
-
|
|
214157
|
-
|
|
214158
|
-
|
|
214159
|
-
|
|
214160
|
-
|
|
214161
|
-
|
|
214162
|
-
|
|
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.
|
|
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.
|
|
214318
|
+
await this.wsSubscription.unsubscribe();
|
|
214191
214319
|
} catch {}
|
|
214192
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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"
|
|
215370
|
+
if (m6.role !== "user")
|
|
215233
215371
|
continue;
|
|
215234
215372
|
let text = "";
|
|
215235
|
-
|
|
215236
|
-
|
|
215237
|
-
|
|
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,
|