hive-intelligence 1.14.0 → 1.16.0
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 -3
- package/agent-skills/packs/hive/SKILL.md +2 -2
- package/agent-skills/packs/hive-prediction-markets/SKILL.md +9 -8
- package/build/{api-client-POBN6Y5S.js → api-client-MK3CVNCH.js} +2 -2
- package/build/{auth-6XVL5CE3.js → auth-TZQC54EJ.js} +1 -1
- package/build/{call-args-BUB6YNHW.js → call-args-4YRVKOLV.js} +2 -2
- package/build/{chunk-ZSPPJW4G.js → chunk-4U4IL5KA.js} +15 -1
- package/build/{chunk-KQVPKTEO.js → chunk-AVTIJGWS.js} +2 -2
- package/build/{chunk-SMVHHINA.js → chunk-B53GNIWW.js} +1 -1
- package/build/chunk-MYSS5SBE.js +71 -0
- package/build/{chunk-YLHNUAFT.js → chunk-O5YKCM67.js} +22 -6
- package/build/{chunk-APBLB62S.js → chunk-PODCJTBV.js} +1 -1
- package/build/{chunk-C4BFUIEB.js → chunk-QSKFAT7Z.js} +2141 -915
- package/build/{chunk-RNVZIPNY.js → chunk-VO6NWVJD.js} +1 -1
- package/build/{chunk-AZFOTKS2.js → chunk-WZ2NMSV3.js} +1 -1
- package/build/cli.js +31 -29
- package/build/{doctor-FPB5TEBU.js → doctor-UKOGXRSW.js} +4 -4
- package/build/{errors-YQAVCXXO.js → errors-BPXK3KCR.js} +1 -1
- package/build/{feedback-G2DFCCI6.js → feedback-6XX2R75U.js} +2 -2
- package/build/{init-all-UDWHV2ER.js → init-all-XDQRGJ3D.js} +1 -1
- package/build/{mcpServer-RGUKLIP2.js → mcpServer-77FPS534.js} +2 -1
- package/build/monitor-worker.js +4 -154
- package/build/release.json +4 -4
- package/build/{routing-4VKAVRYR.js → routing-S7DPEROC.js} +2 -2
- package/build/{serve-46DQ37YQ.js → serve-2NIEUCJN.js} +6 -4
- package/build/server.js +116 -105
- package/build/stdio.js +2 -1
- package/build/{tools-K2P77L4T.js → tools-NSHQSUIK.js} +5 -5
- package/build/{uninstall-I6ERJ2ED.js → uninstall-CECLA5YZ.js} +2 -2
- package/build/{watch-BUSEPVDM.js → watch-4VHBLAL6.js} +3 -3
- package/package.json +5 -15
|
@@ -5,63 +5,20 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
OAUTH_FIRST_MATERIAL_RECEIPT_EVENT
|
|
7
7
|
} from "./chunk-EPF36Q3Z.js";
|
|
8
|
+
import {
|
|
9
|
+
getCsvEnv,
|
|
10
|
+
getEnvVar,
|
|
11
|
+
getLogLevel,
|
|
12
|
+
getNodeEnv,
|
|
13
|
+
getRequiredEnvVar,
|
|
14
|
+
hasEnvVars,
|
|
15
|
+
loadEnvFile
|
|
16
|
+
} from "./chunk-MYSS5SBE.js";
|
|
8
17
|
import {
|
|
9
18
|
SERVER_NAME,
|
|
10
19
|
SERVER_VERSION
|
|
11
20
|
} from "./chunk-ZXKFJQDE.js";
|
|
12
21
|
|
|
13
|
-
// src/mcpServer.ts
|
|
14
|
-
import dotenv from "dotenv";
|
|
15
|
-
|
|
16
|
-
// src/config/env.ts
|
|
17
|
-
import { z } from "zod";
|
|
18
|
-
var nodeEnvSchema = z.enum(["development", "production", "test"]).catch("development");
|
|
19
|
-
var logLevelSchema = z.enum(["debug", "info", "warn", "error"]).catch("info");
|
|
20
|
-
var portSchema = z.coerce.number().int().min(1).max(65535);
|
|
21
|
-
var rateLimitMaxSchema = z.coerce.number().int().min(1).max(1e4);
|
|
22
|
-
function getEnvVar(name) {
|
|
23
|
-
const value = process.env[name]?.trim();
|
|
24
|
-
return value ? value : void 0;
|
|
25
|
-
}
|
|
26
|
-
function getRequiredEnvVar(name, message) {
|
|
27
|
-
const value = getEnvVar(name);
|
|
28
|
-
if (!value) {
|
|
29
|
-
throw new Error(message ?? `Missing required environment variable: ${name}`);
|
|
30
|
-
}
|
|
31
|
-
return value;
|
|
32
|
-
}
|
|
33
|
-
function hasEnvVars(...names) {
|
|
34
|
-
return names.every((name) => Boolean(getEnvVar(name)));
|
|
35
|
-
}
|
|
36
|
-
function getCsvEnv(name) {
|
|
37
|
-
const value = getEnvVar(name);
|
|
38
|
-
if (!value) return [];
|
|
39
|
-
return value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
40
|
-
}
|
|
41
|
-
function getNodeEnv() {
|
|
42
|
-
return nodeEnvSchema.parse(process.env.NODE_ENV);
|
|
43
|
-
}
|
|
44
|
-
function isProduction() {
|
|
45
|
-
return getNodeEnv() === "production";
|
|
46
|
-
}
|
|
47
|
-
function getLogLevel() {
|
|
48
|
-
return logLevelSchema.parse(process.env.LOG_LEVEL);
|
|
49
|
-
}
|
|
50
|
-
function getPort() {
|
|
51
|
-
return portSchema.parse(process.env.PORT ?? 8080);
|
|
52
|
-
}
|
|
53
|
-
function getRateLimitMax() {
|
|
54
|
-
return rateLimitMaxSchema.parse(process.env.RATE_LIMIT_MAX ?? 30);
|
|
55
|
-
}
|
|
56
|
-
function getCredentialedRateLimitMax() {
|
|
57
|
-
return rateLimitMaxSchema.parse(
|
|
58
|
-
process.env.CREDENTIALED_RATE_LIMIT_MAX ?? 1500
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
function getCorsAllowedOrigins() {
|
|
62
|
-
return getCsvEnv("CORS_ALLOWED_ORIGINS");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
22
|
// src/tools/types.ts
|
|
66
23
|
function asTextContentResult(result2, isError = false) {
|
|
67
24
|
const jsonString = JSON.stringify(result2);
|
|
@@ -8841,8 +8798,8 @@ Don't use when:
|
|
|
8841
8798
|
annotations: VOLATILE_READ_ANNOTATIONS
|
|
8842
8799
|
};
|
|
8843
8800
|
var handler91 = async (client2, args) => {
|
|
8844
|
-
const
|
|
8845
|
-
if (
|
|
8801
|
+
const invalid3 = walletWindowError(args);
|
|
8802
|
+
if (invalid3) return invalid3;
|
|
8846
8803
|
const network = encodeURIComponent(String(args?.network ?? "").trim());
|
|
8847
8804
|
const address = encodeURIComponent(String(args?.address ?? "").trim());
|
|
8848
8805
|
const query = {
|
|
@@ -8926,8 +8883,8 @@ Don't use when:
|
|
|
8926
8883
|
annotations: VOLATILE_READ_ANNOTATIONS
|
|
8927
8884
|
};
|
|
8928
8885
|
var handler92 = async (client2, args) => {
|
|
8929
|
-
const
|
|
8930
|
-
if (
|
|
8886
|
+
const invalid3 = walletWindowError(args);
|
|
8887
|
+
if (invalid3) return invalid3;
|
|
8931
8888
|
const network = encodeURIComponent(String(args?.network ?? "").trim());
|
|
8932
8889
|
const address = encodeURIComponent(String(args?.address ?? "").trim());
|
|
8933
8890
|
const query = {
|
|
@@ -9017,7 +8974,7 @@ Use when:
|
|
|
9017
8974
|
|
|
9018
8975
|
Don't use when:
|
|
9019
8976
|
- You need prices or market caps (use coingecko_get_rwa_markets, which returns spot market data in one call)
|
|
9020
|
-
- You need RWA perpetual futures, open interest, or funding rates on Hyperliquid,
|
|
8977
|
+
- You need RWA perpetual futures, open interest, or funding rates on Hyperliquid, Lighter, or Extended (use rwa_get_markets or rwa_get_funding_rates)
|
|
9021
8978
|
- You need the underlying token contracts for an asset (use coingecko_get_rwa_asset with tokens=true)
|
|
9022
8979
|
- You want a regular crypto coin ID (use coingecko_get_coins_list)
|
|
9023
8980
|
|
|
@@ -9115,7 +9072,7 @@ Use when:
|
|
|
9115
9072
|
- You want to size an issuer such as Ondo, Backed, or Paxos by the assets it wraps
|
|
9116
9073
|
|
|
9117
9074
|
Don't use when:
|
|
9118
|
-
- You need RWA perpetual markets or funding rates on Hyperliquid,
|
|
9075
|
+
- You need RWA perpetual markets or funding rates on Hyperliquid, Lighter, or Extended (use rwa_get_markets or rwa_get_funding_rates)
|
|
9119
9076
|
- You need per-exchange or per-pair trades for one asset (use coingecko_get_rwa_tickers)
|
|
9120
9077
|
- You need price history rather than a snapshot (use coingecko_get_rwa_market_chart)
|
|
9121
9078
|
- You need the token contract addresses behind an asset (use coingecko_get_rwa_asset with tokens=true)
|
|
@@ -9351,7 +9308,7 @@ Use when:
|
|
|
9351
9308
|
|
|
9352
9309
|
Don't use when:
|
|
9353
9310
|
- You need a single aggregated price or market cap (use coingecko_get_rwa_markets or coingecko_get_rwa_asset)
|
|
9354
|
-
- You need RWA perpetual futures venues, open interest, or funding rates on Hyperliquid,
|
|
9311
|
+
- You need RWA perpetual futures venues, open interest, or funding rates on Hyperliquid, Lighter, or Extended (use rwa_get_markets or rwa_get_funding_rates)
|
|
9355
9312
|
- You need historical prices (use coingecko_get_rwa_market_chart)
|
|
9356
9313
|
- You want tickers for a regular crypto coin (use coingecko_get_coin_tickers)
|
|
9357
9314
|
|
|
@@ -9696,7 +9653,7 @@ var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
|
9696
9653
|
"GOPLUS_API_KEY"
|
|
9697
9654
|
]);
|
|
9698
9655
|
function isSensitiveEnvironmentKey(key) {
|
|
9699
|
-
return /(?:api[_-]?(?:key|id)|key|secret|password|token|auth|credential|private|bearer|redis.*url|
|
|
9656
|
+
return /(?:api[_-]?(?:key|id)|key|secret|password|token|auth|credential|private|bearer|redis.*url|clickhouse_url)/i.test(
|
|
9700
9657
|
key
|
|
9701
9658
|
);
|
|
9702
9659
|
}
|
|
@@ -18634,65 +18591,161 @@ var wrapper = {
|
|
|
18634
18591
|
]
|
|
18635
18592
|
};
|
|
18636
18593
|
|
|
18637
|
-
// src/
|
|
18638
|
-
|
|
18639
|
-
|
|
18640
|
-
|
|
18641
|
-
|
|
18642
|
-
|
|
18643
|
-
|
|
18644
|
-
|
|
18645
|
-
|
|
18646
|
-
}
|
|
18647
|
-
|
|
18648
|
-
|
|
18649
|
-
|
|
18650
|
-
|
|
18651
|
-
|
|
18652
|
-
);
|
|
18653
|
-
|
|
18654
|
-
|
|
18655
|
-
|
|
18656
|
-
|
|
18657
|
-
|
|
18658
|
-
|
|
18659
|
-
|
|
18660
|
-
|
|
18661
|
-
|
|
18662
|
-
})
|
|
18663
|
-
|
|
18664
|
-
|
|
18665
|
-
|
|
18666
|
-
|
|
18667
|
-
|
|
18668
|
-
|
|
18669
|
-
|
|
18670
|
-
|
|
18671
|
-
|
|
18672
|
-
|
|
18673
|
-
|
|
18674
|
-
|
|
18675
|
-
|
|
18676
|
-
|
|
18677
|
-
|
|
18678
|
-
|
|
18679
|
-
|
|
18680
|
-
|
|
18681
|
-
|
|
18682
|
-
|
|
18683
|
-
|
|
18684
|
-
|
|
18685
|
-
|
|
18686
|
-
|
|
18687
|
-
|
|
18688
|
-
|
|
18689
|
-
|
|
18690
|
-
|
|
18691
|
-
|
|
18692
|
-
|
|
18594
|
+
// src/utils/fetchClient.ts
|
|
18595
|
+
var HttpError = class extends Error {
|
|
18596
|
+
config;
|
|
18597
|
+
constructor(message, config2, code, response) {
|
|
18598
|
+
super(message);
|
|
18599
|
+
this.name = "HttpError";
|
|
18600
|
+
this.config = config2;
|
|
18601
|
+
if (code) this.code = code;
|
|
18602
|
+
if (response) this.response = response;
|
|
18603
|
+
}
|
|
18604
|
+
};
|
|
18605
|
+
function encode(value) {
|
|
18606
|
+
return encodeURIComponent(value).replace(/%3A/gi, ":").replace(/%24/g, "$").replace(/%2C/gi, ",").replace(/%20/g, "+");
|
|
18607
|
+
}
|
|
18608
|
+
function appendParam(pairs, key, value) {
|
|
18609
|
+
if (value === null || value === void 0) return;
|
|
18610
|
+
if (Array.isArray(value)) {
|
|
18611
|
+
for (const item of value) appendParam(pairs, `${key}[]`, item);
|
|
18612
|
+
} else if (value instanceof Date) {
|
|
18613
|
+
pairs.push(`${encode(key)}=${encode(value.toISOString())}`);
|
|
18614
|
+
} else if (typeof value === "object") {
|
|
18615
|
+
for (const [sub, item] of Object.entries(value)) {
|
|
18616
|
+
appendParam(pairs, `${key}[${sub}]`, item);
|
|
18617
|
+
}
|
|
18618
|
+
} else {
|
|
18619
|
+
pairs.push(`${encode(key)}=${encode(String(value))}`);
|
|
18620
|
+
}
|
|
18621
|
+
}
|
|
18622
|
+
function buildUrl(baseURL, url, params) {
|
|
18623
|
+
const full = !baseURL || /^[a-z][a-z\d+.-]*:\/\//i.test(url) ? url : url ? `${baseURL.replace(/\/+$/, "")}/${url.replace(/^\/+/, "")}` : baseURL;
|
|
18624
|
+
const pairs = [];
|
|
18625
|
+
for (const [key, value] of Object.entries(params ?? {})) {
|
|
18626
|
+
appendParam(pairs, key, value);
|
|
18627
|
+
}
|
|
18628
|
+
if (pairs.length === 0) return full;
|
|
18629
|
+
return `${full}${full.includes("?") ? "&" : "?"}${pairs.join("&")}`;
|
|
18630
|
+
}
|
|
18631
|
+
var UNDICI_CODES = {
|
|
18632
|
+
UND_ERR_CONNECT_TIMEOUT: "ETIMEDOUT",
|
|
18633
|
+
UND_ERR_HEADERS_TIMEOUT: "ETIMEDOUT",
|
|
18634
|
+
UND_ERR_BODY_TIMEOUT: "ETIMEDOUT",
|
|
18635
|
+
UND_ERR_SOCKET: "ECONNRESET"
|
|
18636
|
+
};
|
|
18637
|
+
function networkError(error, timedOut, timeout, config2) {
|
|
18638
|
+
if (timedOut) {
|
|
18639
|
+
return new HttpError(`timeout of ${timeout}ms exceeded`, config2, "ECONNABORTED");
|
|
18640
|
+
}
|
|
18641
|
+
const cause = error?.cause;
|
|
18642
|
+
const rawCode = cause?.code;
|
|
18643
|
+
const code = (rawCode && UNDICI_CODES[rawCode]) ?? rawCode ?? "ERR_NETWORK";
|
|
18644
|
+
return new HttpError(cause?.message || rawCode || "Network Error", config2, code);
|
|
18645
|
+
}
|
|
18646
|
+
async function readBody(response, maxContentLength, config2, onChunk) {
|
|
18647
|
+
if (!response.body) return "";
|
|
18648
|
+
const reader = response.body.getReader();
|
|
18649
|
+
const chunks = [];
|
|
18650
|
+
let size = 0;
|
|
18651
|
+
for (; ; ) {
|
|
18652
|
+
const { done, value } = await reader.read();
|
|
18653
|
+
if (done) break;
|
|
18654
|
+
onChunk();
|
|
18655
|
+
size += value.byteLength;
|
|
18656
|
+
if (maxContentLength && size > maxContentLength) {
|
|
18657
|
+
await reader.cancel();
|
|
18658
|
+
throw new HttpError(
|
|
18659
|
+
`maxContentLength size of ${maxContentLength} exceeded`,
|
|
18660
|
+
config2,
|
|
18661
|
+
"ERR_BAD_RESPONSE"
|
|
18662
|
+
);
|
|
18663
|
+
}
|
|
18664
|
+
chunks.push(value);
|
|
18665
|
+
}
|
|
18666
|
+
return new TextDecoder().decode(Buffer.concat(chunks));
|
|
18667
|
+
}
|
|
18668
|
+
function parseBody(text) {
|
|
18669
|
+
if (!text) return text;
|
|
18670
|
+
try {
|
|
18671
|
+
return JSON.parse(text);
|
|
18672
|
+
} catch {
|
|
18673
|
+
return text;
|
|
18674
|
+
}
|
|
18675
|
+
}
|
|
18676
|
+
function createFetchClient(options = {}) {
|
|
18677
|
+
async function request(method, url, data, config2 = {}) {
|
|
18678
|
+
const info = { method, url };
|
|
18679
|
+
const timeout = config2.timeout ?? options.timeout ?? 0;
|
|
18680
|
+
const controller = new AbortController();
|
|
18681
|
+
let timedOut = false;
|
|
18682
|
+
let timer;
|
|
18683
|
+
const armTimeout = () => {
|
|
18684
|
+
clearTimeout(timer);
|
|
18685
|
+
if (timeout <= 0) return;
|
|
18686
|
+
timer = setTimeout(() => {
|
|
18687
|
+
timedOut = true;
|
|
18688
|
+
controller.abort();
|
|
18689
|
+
}, timeout);
|
|
18690
|
+
};
|
|
18691
|
+
armTimeout();
|
|
18692
|
+
try {
|
|
18693
|
+
options.onRequest?.({ method, url, data });
|
|
18694
|
+
const headers = {
|
|
18695
|
+
Accept: "application/json, text/plain, */*",
|
|
18696
|
+
...options.headers,
|
|
18697
|
+
...config2.headers
|
|
18698
|
+
};
|
|
18699
|
+
let body;
|
|
18700
|
+
if (data !== void 0) {
|
|
18701
|
+
body = typeof data === "string" ? data : JSON.stringify(data);
|
|
18702
|
+
const hasContentType = Object.keys(headers).some(
|
|
18703
|
+
(name) => name.toLowerCase() === "content-type"
|
|
18704
|
+
);
|
|
18705
|
+
if (!hasContentType) headers["Content-Type"] = "application/json";
|
|
18706
|
+
}
|
|
18707
|
+
let text;
|
|
18708
|
+
let res;
|
|
18709
|
+
try {
|
|
18710
|
+
res = await fetch(buildUrl(options.baseURL, url, config2.params), {
|
|
18711
|
+
method: method.toUpperCase(),
|
|
18712
|
+
headers,
|
|
18713
|
+
body,
|
|
18714
|
+
signal: controller.signal
|
|
18715
|
+
});
|
|
18716
|
+
armTimeout();
|
|
18717
|
+
text = await readBody(res, options.maxContentLength, info, armTimeout);
|
|
18718
|
+
} catch (error) {
|
|
18719
|
+
if (error instanceof HttpError) throw error;
|
|
18720
|
+
throw networkError(error, timedOut, timeout, info);
|
|
18721
|
+
}
|
|
18722
|
+
const response = {
|
|
18723
|
+
data: parseBody(text),
|
|
18724
|
+
status: res.status,
|
|
18725
|
+
headers: Object.fromEntries(res.headers)
|
|
18726
|
+
};
|
|
18727
|
+
if (res.status < 200 || res.status >= 300) {
|
|
18728
|
+
throw new HttpError(
|
|
18729
|
+
`Request failed with status code ${res.status}`,
|
|
18730
|
+
info,
|
|
18731
|
+
res.status < 500 ? "ERR_BAD_REQUEST" : "ERR_BAD_RESPONSE",
|
|
18732
|
+
response
|
|
18733
|
+
);
|
|
18734
|
+
}
|
|
18735
|
+
return response;
|
|
18736
|
+
} catch (error) {
|
|
18737
|
+
options.onError?.(error);
|
|
18738
|
+
throw error;
|
|
18739
|
+
} finally {
|
|
18740
|
+
clearTimeout(timer);
|
|
18741
|
+
}
|
|
18693
18742
|
}
|
|
18694
|
-
|
|
18695
|
-
|
|
18743
|
+
return {
|
|
18744
|
+
get: (url, config2) => request("get", url, void 0, config2),
|
|
18745
|
+
post: (url, data, config2) => request("post", url, data, config2),
|
|
18746
|
+
put: (url, data, config2) => request("put", url, data, config2),
|
|
18747
|
+
delete: (url, config2) => request("delete", url, void 0, config2)
|
|
18748
|
+
};
|
|
18696
18749
|
}
|
|
18697
18750
|
|
|
18698
18751
|
// src/tools/defillamaTools/api/defillama.ts
|
|
@@ -18701,69 +18754,60 @@ var DeFiLlamaAPI = class {
|
|
|
18701
18754
|
stablecoinsClient;
|
|
18702
18755
|
yieldsClient;
|
|
18703
18756
|
coinsClient;
|
|
18704
|
-
logger =
|
|
18757
|
+
logger = secureLogger;
|
|
18705
18758
|
baseURL = "https://api.llama.fi";
|
|
18706
18759
|
stablecoinsURL = "https://stablecoins.llama.fi";
|
|
18707
18760
|
yieldsURL = "https://yields.llama.fi";
|
|
18708
18761
|
coinsURL = "https://coins.llama.fi";
|
|
18709
18762
|
constructor() {
|
|
18710
|
-
|
|
18763
|
+
const errorHandler = (error) => {
|
|
18764
|
+
const status = error.response?.status;
|
|
18765
|
+
const message = error.response?.data?.message || error.message;
|
|
18766
|
+
if (status === 402) {
|
|
18767
|
+
throw new Error(
|
|
18768
|
+
`DeFiLlama API Error: this endpoint requires a DeFiLlama Pro plan subscription (HTTP 402); retrying on the free API will not succeed. ${message}`
|
|
18769
|
+
);
|
|
18770
|
+
}
|
|
18771
|
+
throw new Error(`DeFiLlama API Error: ${message}`);
|
|
18772
|
+
};
|
|
18773
|
+
this.client = createFetchClient({
|
|
18711
18774
|
baseURL: this.baseURL,
|
|
18712
18775
|
timeout: 6e4,
|
|
18713
18776
|
// Increased from 30s to 60s for large responses
|
|
18714
18777
|
headers: {
|
|
18715
18778
|
"Content-Type": "application/json",
|
|
18716
18779
|
"User-Agent": "defillama-mcp-server/1.0.0"
|
|
18717
|
-
}
|
|
18780
|
+
},
|
|
18781
|
+
onError: errorHandler
|
|
18718
18782
|
});
|
|
18719
|
-
this.stablecoinsClient =
|
|
18783
|
+
this.stablecoinsClient = createFetchClient({
|
|
18720
18784
|
baseURL: this.stablecoinsURL,
|
|
18721
18785
|
timeout: 3e4,
|
|
18722
18786
|
// Reduced to 30s for MCP production
|
|
18723
18787
|
headers: {
|
|
18724
18788
|
"Content-Type": "application/json",
|
|
18725
18789
|
"User-Agent": "defillama-mcp-server/1.0.0"
|
|
18726
|
-
}
|
|
18790
|
+
},
|
|
18791
|
+
onError: errorHandler
|
|
18727
18792
|
});
|
|
18728
|
-
this.yieldsClient =
|
|
18793
|
+
this.yieldsClient = createFetchClient({
|
|
18729
18794
|
baseURL: this.yieldsURL,
|
|
18730
18795
|
timeout: 6e4,
|
|
18731
18796
|
headers: {
|
|
18732
18797
|
"Content-Type": "application/json",
|
|
18733
18798
|
"User-Agent": "defillama-mcp-server/1.0.0"
|
|
18734
|
-
}
|
|
18799
|
+
},
|
|
18800
|
+
onError: errorHandler
|
|
18735
18801
|
});
|
|
18736
|
-
this.coinsClient =
|
|
18802
|
+
this.coinsClient = createFetchClient({
|
|
18737
18803
|
baseURL: this.coinsURL,
|
|
18738
18804
|
timeout: 3e4,
|
|
18739
18805
|
headers: {
|
|
18740
18806
|
"Content-Type": "application/json",
|
|
18741
18807
|
"User-Agent": "defillama-mcp-server/1.0.0"
|
|
18742
|
-
}
|
|
18808
|
+
},
|
|
18809
|
+
onError: errorHandler
|
|
18743
18810
|
});
|
|
18744
|
-
const errorHandler = (error) => {
|
|
18745
|
-
const status = error.response?.status;
|
|
18746
|
-
const message = error.response?.data?.message || error.message;
|
|
18747
|
-
if (status === 402) {
|
|
18748
|
-
throw new Error(
|
|
18749
|
-
`DeFiLlama API Error: this endpoint requires a DeFiLlama Pro plan subscription (HTTP 402); retrying on the free API will not succeed. ${message}`
|
|
18750
|
-
);
|
|
18751
|
-
}
|
|
18752
|
-
throw new Error(`DeFiLlama API Error: ${message}`);
|
|
18753
|
-
};
|
|
18754
|
-
this.client.interceptors.response.use((response) => response, errorHandler);
|
|
18755
|
-
this.stablecoinsClient.interceptors.response.use(
|
|
18756
|
-
(response) => response,
|
|
18757
|
-
errorHandler
|
|
18758
|
-
);
|
|
18759
|
-
this.yieldsClient.interceptors.response.use(
|
|
18760
|
-
(response) => response,
|
|
18761
|
-
errorHandler
|
|
18762
|
-
);
|
|
18763
|
-
this.coinsClient.interceptors.response.use(
|
|
18764
|
-
(response) => response,
|
|
18765
|
-
errorHandler
|
|
18766
|
-
);
|
|
18767
18811
|
}
|
|
18768
18812
|
/**
|
|
18769
18813
|
* Helper method to log API calls with timing and response details
|
|
@@ -19424,7 +19468,7 @@ var DeFiLlamaAPI = class {
|
|
|
19424
19468
|
});
|
|
19425
19469
|
return response.data;
|
|
19426
19470
|
} catch (error) {
|
|
19427
|
-
if (
|
|
19471
|
+
if (error instanceof HttpError) {
|
|
19428
19472
|
if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
|
|
19429
19473
|
throw new Error(
|
|
19430
19474
|
`Timeout while fetching DEX volume data for ${dexName}. The response may be too large.`
|
|
@@ -20416,8 +20460,8 @@ Error Handling:
|
|
|
20416
20460
|
},
|
|
20417
20461
|
async execute(args) {
|
|
20418
20462
|
const MAX_SAFE_LIMIT = 50;
|
|
20419
|
-
const
|
|
20420
|
-
const limit = Math.min(args?.limit ||
|
|
20463
|
+
const DEFAULT_LIMIT5 = 30;
|
|
20464
|
+
const limit = Math.min(args?.limit || DEFAULT_LIMIT5, MAX_SAFE_LIMIT);
|
|
20421
20465
|
const offset = args?.offset || 0;
|
|
20422
20466
|
try {
|
|
20423
20467
|
const data = await apiClient.getProtocols();
|
|
@@ -21525,8 +21569,8 @@ Error Handling:
|
|
|
21525
21569
|
},
|
|
21526
21570
|
async execute(args) {
|
|
21527
21571
|
const MAX_SAFE_LIMIT = 100;
|
|
21528
|
-
const
|
|
21529
|
-
const limit = Math.min(args?.limit ||
|
|
21572
|
+
const DEFAULT_LIMIT5 = 50;
|
|
21573
|
+
const limit = Math.min(args?.limit || DEFAULT_LIMIT5, MAX_SAFE_LIMIT);
|
|
21530
21574
|
const offset = args?.offset || 0;
|
|
21531
21575
|
const minTvl = args?.minTvl || 0;
|
|
21532
21576
|
try {
|
|
@@ -23642,8 +23686,8 @@ Error Handling:
|
|
|
23642
23686
|
},
|
|
23643
23687
|
async execute(args) {
|
|
23644
23688
|
const MAX_SAFE_LIMIT = 100;
|
|
23645
|
-
const
|
|
23646
|
-
const limit = Math.min(args.limit ||
|
|
23689
|
+
const DEFAULT_LIMIT5 = 50;
|
|
23690
|
+
const limit = Math.min(args.limit || DEFAULT_LIMIT5, MAX_SAFE_LIMIT);
|
|
23647
23691
|
const offset = args.offset || 0;
|
|
23648
23692
|
const minFees = args.minFees || 0;
|
|
23649
23693
|
try {
|
|
@@ -31492,9 +31536,6 @@ function getAllCCXTEndpoints() {
|
|
|
31492
31536
|
}
|
|
31493
31537
|
|
|
31494
31538
|
// src/utils/httpClient.ts
|
|
31495
|
-
import axios2, {
|
|
31496
|
-
AxiosError
|
|
31497
|
-
} from "axios";
|
|
31498
31539
|
var circuitBreakers = /* @__PURE__ */ new Map();
|
|
31499
31540
|
function getCircuitBreaker(serviceName) {
|
|
31500
31541
|
if (!circuitBreakers.has(serviceName)) {
|
|
@@ -31526,7 +31567,7 @@ function upstreamDetail(error) {
|
|
|
31526
31567
|
return compact ? compact.slice(0, 240) : null;
|
|
31527
31568
|
}
|
|
31528
31569
|
function withUpstreamDetail(error) {
|
|
31529
|
-
if (!(error instanceof
|
|
31570
|
+
if (!(error instanceof HttpError)) return error;
|
|
31530
31571
|
const detail = upstreamDetail(error);
|
|
31531
31572
|
if (detail && !error.message.includes(detail)) {
|
|
31532
31573
|
error.message = `${error.message}: ${detail}`;
|
|
@@ -31535,7 +31576,7 @@ function withUpstreamDetail(error) {
|
|
|
31535
31576
|
}
|
|
31536
31577
|
function failureMessage(error) {
|
|
31537
31578
|
let message = "";
|
|
31538
|
-
if (error instanceof
|
|
31579
|
+
if (error instanceof HttpError) {
|
|
31539
31580
|
message = upstreamDetail(error) ?? error.message;
|
|
31540
31581
|
} else if (error instanceof Error) {
|
|
31541
31582
|
message = error.message;
|
|
@@ -31568,7 +31609,7 @@ function sleep(ms) {
|
|
|
31568
31609
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
31569
31610
|
}
|
|
31570
31611
|
var ResilientHttpClient = class {
|
|
31571
|
-
|
|
31612
|
+
http;
|
|
31572
31613
|
config;
|
|
31573
31614
|
circuitBreaker;
|
|
31574
31615
|
constructor(config2) {
|
|
@@ -31584,10 +31625,10 @@ var ResilientHttpClient = class {
|
|
|
31584
31625
|
circuitBreakerThreshold: config2.circuitBreakerThreshold ?? 5,
|
|
31585
31626
|
circuitBreakerResetTimeout: config2.circuitBreakerResetTimeout ?? 3e4
|
|
31586
31627
|
};
|
|
31587
|
-
this.
|
|
31628
|
+
this.http = createFetchClient({
|
|
31588
31629
|
baseURL: this.config.baseURL,
|
|
31589
31630
|
timeout: this.config.timeout,
|
|
31590
|
-
|
|
31631
|
+
maxContentLength: config2.maxContentLength,
|
|
31591
31632
|
headers: {
|
|
31592
31633
|
"Content-Type": "application/json",
|
|
31593
31634
|
Accept: "application/json",
|
|
@@ -31595,35 +31636,28 @@ var ResilientHttpClient = class {
|
|
|
31595
31636
|
}
|
|
31596
31637
|
});
|
|
31597
31638
|
this.circuitBreaker = getCircuitBreaker(this.config.serviceName);
|
|
31598
|
-
|
|
31599
|
-
|
|
31600
|
-
|
|
31601
|
-
|
|
31602
|
-
|
|
31603
|
-
|
|
31604
|
-
|
|
31605
|
-
|
|
31606
|
-
|
|
31607
|
-
|
|
31608
|
-
|
|
31609
|
-
|
|
31610
|
-
|
|
31611
|
-
|
|
31612
|
-
|
|
31613
|
-
|
|
31614
|
-
);
|
|
31615
|
-
this.
|
|
31616
|
-
|
|
31617
|
-
|
|
31618
|
-
|
|
31619
|
-
|
|
31620
|
-
});
|
|
31621
|
-
return response;
|
|
31622
|
-
},
|
|
31623
|
-
(error) => {
|
|
31624
|
-
return Promise.reject(error);
|
|
31625
|
-
}
|
|
31626
|
-
);
|
|
31639
|
+
}
|
|
31640
|
+
/**
|
|
31641
|
+
* Send one request, with debug logging on either side. Failures are logged
|
|
31642
|
+
* by the retry logic.
|
|
31643
|
+
*/
|
|
31644
|
+
async send(method, endpoint, options) {
|
|
31645
|
+
secureLogger.debug(`[${this.config.serviceName}] Request`, {
|
|
31646
|
+
method: method.toUpperCase(),
|
|
31647
|
+
url: endpoint,
|
|
31648
|
+
params: options.params
|
|
31649
|
+
});
|
|
31650
|
+
const config2 = {
|
|
31651
|
+
params: options.params,
|
|
31652
|
+
timeout: options.timeout ?? this.config.timeout,
|
|
31653
|
+
headers: options.headers
|
|
31654
|
+
};
|
|
31655
|
+
const response = method === "get" || method === "delete" ? await this.http[method](endpoint, config2) : await this.http[method](endpoint, options.body, config2);
|
|
31656
|
+
secureLogger.debug(`[${this.config.serviceName}] Response`, {
|
|
31657
|
+
status: response.status,
|
|
31658
|
+
url: endpoint
|
|
31659
|
+
});
|
|
31660
|
+
return response;
|
|
31627
31661
|
}
|
|
31628
31662
|
/**
|
|
31629
31663
|
* Check if the circuit breaker allows the request
|
|
@@ -31706,7 +31740,7 @@ var ResilientHttpClient = class {
|
|
|
31706
31740
|
return response.data;
|
|
31707
31741
|
} catch (error) {
|
|
31708
31742
|
lastError = error;
|
|
31709
|
-
if (error instanceof
|
|
31743
|
+
if (error instanceof HttpError && isRetryableError(error)) {
|
|
31710
31744
|
this.recordFailure(error);
|
|
31711
31745
|
if (attempt < maxRetries) {
|
|
31712
31746
|
const delay2 = calculateBackoffDelay(
|
|
@@ -31727,7 +31761,7 @@ var ResilientHttpClient = class {
|
|
|
31727
31761
|
continue;
|
|
31728
31762
|
}
|
|
31729
31763
|
} else {
|
|
31730
|
-
if (error instanceof
|
|
31764
|
+
if (error instanceof HttpError) {
|
|
31731
31765
|
const status = error.response?.status ?? 0;
|
|
31732
31766
|
const oversized = error.message.includes("maxContentLength");
|
|
31733
31767
|
if (!status && !oversized || status >= 500 || status === 429) {
|
|
@@ -31735,7 +31769,7 @@ var ResilientHttpClient = class {
|
|
|
31735
31769
|
}
|
|
31736
31770
|
}
|
|
31737
31771
|
}
|
|
31738
|
-
const errorDetails = error instanceof
|
|
31772
|
+
const errorDetails = error instanceof HttpError ? {
|
|
31739
31773
|
status: error.response?.status,
|
|
31740
31774
|
code: error.code,
|
|
31741
31775
|
message: error.message,
|
|
@@ -31757,13 +31791,8 @@ var ResilientHttpClient = class {
|
|
|
31757
31791
|
* Perform a GET request
|
|
31758
31792
|
*/
|
|
31759
31793
|
async get(endpoint, options = {}) {
|
|
31760
|
-
const config2 = {
|
|
31761
|
-
params: options.params,
|
|
31762
|
-
timeout: options.timeout ?? this.config.timeout,
|
|
31763
|
-
headers: options.headers
|
|
31764
|
-
};
|
|
31765
31794
|
return this.executeWithRetry(
|
|
31766
|
-
() => this.
|
|
31795
|
+
() => this.send("get", endpoint, options),
|
|
31767
31796
|
options
|
|
31768
31797
|
);
|
|
31769
31798
|
}
|
|
@@ -31771,13 +31800,8 @@ var ResilientHttpClient = class {
|
|
|
31771
31800
|
* Perform a POST request
|
|
31772
31801
|
*/
|
|
31773
31802
|
async post(endpoint, options = {}) {
|
|
31774
|
-
const config2 = {
|
|
31775
|
-
params: options.params,
|
|
31776
|
-
timeout: options.timeout ?? this.config.timeout,
|
|
31777
|
-
headers: options.headers
|
|
31778
|
-
};
|
|
31779
31803
|
return this.executeWithRetry(
|
|
31780
|
-
() => this.
|
|
31804
|
+
() => this.send("post", endpoint, options),
|
|
31781
31805
|
options
|
|
31782
31806
|
);
|
|
31783
31807
|
}
|
|
@@ -31785,13 +31809,8 @@ var ResilientHttpClient = class {
|
|
|
31785
31809
|
* Perform a PUT request
|
|
31786
31810
|
*/
|
|
31787
31811
|
async put(endpoint, options = {}) {
|
|
31788
|
-
const config2 = {
|
|
31789
|
-
params: options.params,
|
|
31790
|
-
timeout: options.timeout ?? this.config.timeout,
|
|
31791
|
-
headers: options.headers
|
|
31792
|
-
};
|
|
31793
31812
|
return this.executeWithRetry(
|
|
31794
|
-
() => this.
|
|
31813
|
+
() => this.send("put", endpoint, options),
|
|
31795
31814
|
options
|
|
31796
31815
|
);
|
|
31797
31816
|
}
|
|
@@ -31799,13 +31818,8 @@ var ResilientHttpClient = class {
|
|
|
31799
31818
|
* Perform a DELETE request
|
|
31800
31819
|
*/
|
|
31801
31820
|
async delete(endpoint, options = {}) {
|
|
31802
|
-
const config2 = {
|
|
31803
|
-
params: options.params,
|
|
31804
|
-
timeout: options.timeout ?? this.config.timeout,
|
|
31805
|
-
headers: options.headers
|
|
31806
|
-
};
|
|
31807
31821
|
return this.executeWithRetry(
|
|
31808
|
-
() => this.
|
|
31822
|
+
() => this.send("delete", endpoint, options),
|
|
31809
31823
|
options
|
|
31810
31824
|
);
|
|
31811
31825
|
}
|
|
@@ -38745,8 +38759,25 @@ function getAllAlchemyEndpoints() {
|
|
|
38745
38759
|
return endpoints;
|
|
38746
38760
|
}
|
|
38747
38761
|
|
|
38762
|
+
// src/config/removedVenues.ts
|
|
38763
|
+
var REMOVED_CCXT_EXCHANGES = /* @__PURE__ */ new Set([
|
|
38764
|
+
"bitmex",
|
|
38765
|
+
"coinex",
|
|
38766
|
+
"coinbaseinternational",
|
|
38767
|
+
"dydx",
|
|
38768
|
+
"woofipro"
|
|
38769
|
+
]);
|
|
38770
|
+
var REMOVED_HYPERLIQUID_DEXES = /* @__PURE__ */ new Set([
|
|
38771
|
+
"km",
|
|
38772
|
+
"mkts",
|
|
38773
|
+
"para",
|
|
38774
|
+
"io"
|
|
38775
|
+
]);
|
|
38776
|
+
function removedVenueMessage(venue) {
|
|
38777
|
+
return `Invalid arguments: Hive does not serve the venue "${venue}" (removed 2026-10-26). Pick another venue.`;
|
|
38778
|
+
}
|
|
38779
|
+
|
|
38748
38780
|
// src/tools/hyperliquid/api/hyperliquid.ts
|
|
38749
|
-
import axios3 from "axios";
|
|
38750
38781
|
var HYPERLIQUID_API_URL = "https://api.hyperliquid.xyz";
|
|
38751
38782
|
var DEFAULT_LEADERBOARD_LIMIT = 20;
|
|
38752
38783
|
var MAX_LEADERBOARD_LIMIT = 500;
|
|
@@ -38767,23 +38798,27 @@ var HyperliquidAPI = class {
|
|
|
38767
38798
|
now;
|
|
38768
38799
|
constructor(now = Date.now) {
|
|
38769
38800
|
this.now = now;
|
|
38770
|
-
this.client =
|
|
38801
|
+
this.client = createFetchClient({
|
|
38771
38802
|
baseURL: HYPERLIQUID_API_URL,
|
|
38772
38803
|
timeout: 3e4,
|
|
38773
38804
|
headers: {
|
|
38774
38805
|
"Content-Type": "application/json",
|
|
38775
38806
|
"User-Agent": "hive-mcp-server/1.0.0"
|
|
38776
|
-
}
|
|
38777
|
-
|
|
38778
|
-
|
|
38779
|
-
|
|
38780
|
-
|
|
38807
|
+
},
|
|
38808
|
+
// One gate for every /info read that scopes to a builder dex.
|
|
38809
|
+
onRequest: ({ data }) => {
|
|
38810
|
+
const dex = data?.dex;
|
|
38811
|
+
if (typeof dex === "string" && REMOVED_HYPERLIQUID_DEXES.has(dex)) {
|
|
38812
|
+
throw new Error(removedVenueMessage(`hyperliquid:${dex}`));
|
|
38813
|
+
}
|
|
38814
|
+
},
|
|
38815
|
+
onError: (error) => {
|
|
38781
38816
|
const err = error;
|
|
38782
38817
|
const message = err?.response?.data?.message || err?.message || "request failed";
|
|
38783
38818
|
secureLogger.warn("Hyperliquid API error", { message });
|
|
38784
38819
|
throw new Error(`Hyperliquid API Error: ${message}`);
|
|
38785
38820
|
}
|
|
38786
|
-
);
|
|
38821
|
+
});
|
|
38787
38822
|
}
|
|
38788
38823
|
/**
|
|
38789
38824
|
* Per-pair 24h notional volume leaderboard for a Hyperliquid perp DEX.
|
|
@@ -38840,7 +38875,9 @@ var HyperliquidAPI = class {
|
|
|
38840
38875
|
name: String(d.name ?? ""),
|
|
38841
38876
|
fullName: d.full_name ?? d.fullName ?? null,
|
|
38842
38877
|
deployer: d.deployer ?? null
|
|
38843
|
-
})).filter(
|
|
38878
|
+
})).filter(
|
|
38879
|
+
(d) => d.name.length > 0 && !REMOVED_HYPERLIQUID_DEXES.has(d.name)
|
|
38880
|
+
);
|
|
38844
38881
|
return { builderDexCount: builderDexs.length, builderDexs };
|
|
38845
38882
|
}
|
|
38846
38883
|
/**
|
|
@@ -39722,7 +39759,7 @@ var HYPERLIQUID_INFO_DESCRIPTORS = [
|
|
|
39722
39759
|
"You need outcome identifiers for hyperliquid_get_settled_outcome"
|
|
39723
39760
|
],
|
|
39724
39761
|
dontUseWhen: [
|
|
39725
|
-
"You need Polymarket markets (use the polymarket_ tools) or Kalshi markets (
|
|
39762
|
+
"You need Polymarket markets (use the polymarket_ tools) or Kalshi markets (use the kalshi_ tools)"
|
|
39726
39763
|
],
|
|
39727
39764
|
params: []
|
|
39728
39765
|
},
|
|
@@ -40482,8 +40519,6 @@ function nextIntervalBoundaryIso(intervalHours, now = Date.now()) {
|
|
|
40482
40519
|
return new Date(Math.ceil(now / ms) * ms).toISOString();
|
|
40483
40520
|
}
|
|
40484
40521
|
var HOURS_PER_YEAR = 24 * 365;
|
|
40485
|
-
var ARBITRUM_BLOCKS_PER_YEAR = 365.25 * 24 * 3600 / 0.25;
|
|
40486
|
-
var OSTIUM_PRECISION_18 = 1e18;
|
|
40487
40522
|
function annualizeFundingRate(ratePerInterval, intervalHours) {
|
|
40488
40523
|
if (ratePerInterval == null || !Number.isFinite(ratePerInterval)) {
|
|
40489
40524
|
return null;
|
|
@@ -40491,16 +40526,6 @@ function annualizeFundingRate(ratePerInterval, intervalHours) {
|
|
|
40491
40526
|
if (!Number.isFinite(intervalHours) || intervalHours <= 0) return null;
|
|
40492
40527
|
return ratePerInterval * (HOURS_PER_YEAR / intervalHours);
|
|
40493
40528
|
}
|
|
40494
|
-
function ostiumRolloverAnnualFraction(rolloverFeePerBlockRaw) {
|
|
40495
|
-
if (!rolloverFeePerBlockRaw) return null;
|
|
40496
|
-
const perBlock = Number(rolloverFeePerBlockRaw) / OSTIUM_PRECISION_18;
|
|
40497
|
-
if (!Number.isFinite(perBlock) || perBlock < 0) return null;
|
|
40498
|
-
return perBlock * ARBITRUM_BLOCKS_PER_YEAR;
|
|
40499
|
-
}
|
|
40500
|
-
function avantisMarginFeeAnnualFraction(percentPerHour) {
|
|
40501
|
-
if (percentPerHour == null || !Number.isFinite(percentPerHour)) return null;
|
|
40502
|
-
return percentPerHour * HOURS_PER_YEAR / 100;
|
|
40503
|
-
}
|
|
40504
40529
|
var ETFS = /* @__PURE__ */ new Set([
|
|
40505
40530
|
"SPY",
|
|
40506
40531
|
"QQQ",
|
|
@@ -40749,117 +40774,6 @@ function stampPriceSuspect(rows) {
|
|
|
40749
40774
|
return rows;
|
|
40750
40775
|
}
|
|
40751
40776
|
|
|
40752
|
-
// src/tools/rwa/venues/avantis.ts
|
|
40753
|
-
var AVANTIS_DATA_URL = "https://socket-api-pub.avantisfi.com/socket-api/v1/data";
|
|
40754
|
-
var AVANTIS_EQUITIES_GROUP_NAME = "EQUITIES";
|
|
40755
|
-
var AVANTIS_EQUITY_ASSET_TYPE = "equity";
|
|
40756
|
-
var AVANTIS_QUOTE_INTERVAL_HOURS = 1;
|
|
40757
|
-
function resolveEquitiesGroupIndex(groupInfo) {
|
|
40758
|
-
for (const [index, group] of Object.entries(groupInfo ?? {})) {
|
|
40759
|
-
if (group?.name?.toUpperCase() === AVANTIS_EQUITIES_GROUP_NAME) {
|
|
40760
|
-
const parsed = parseFiniteNumber(index);
|
|
40761
|
-
if (parsed != null) return parsed;
|
|
40762
|
-
}
|
|
40763
|
-
}
|
|
40764
|
-
return null;
|
|
40765
|
-
}
|
|
40766
|
-
async function fetchAvantisRows(fetchJson = defaultRwaFetchJson) {
|
|
40767
|
-
const payload = await fetchJson(
|
|
40768
|
-
AVANTIS_DATA_URL,
|
|
40769
|
-
void 0,
|
|
40770
|
-
"avantis socket-api data"
|
|
40771
|
-
);
|
|
40772
|
-
const data = payload?.data;
|
|
40773
|
-
const pairInfos = data?.pairInfos;
|
|
40774
|
-
if (!pairInfos || typeof pairInfos !== "object") {
|
|
40775
|
-
throw new Error("avantis socket-api returned unexpected shape");
|
|
40776
|
-
}
|
|
40777
|
-
const equitiesGroupIndex = resolveEquitiesGroupIndex(data?.groupInfo);
|
|
40778
|
-
if (equitiesGroupIndex == null) {
|
|
40779
|
-
throw new Error("avantis socket-api exposed no EQUITIES group");
|
|
40780
|
-
}
|
|
40781
|
-
const equities = [];
|
|
40782
|
-
let equitiesGroupPairCount = 0;
|
|
40783
|
-
for (const [key, pair] of Object.entries(pairInfos)) {
|
|
40784
|
-
if (pair?.groupIndex !== equitiesGroupIndex) continue;
|
|
40785
|
-
equitiesGroupPairCount += 1;
|
|
40786
|
-
const attributes = pair.feed?.attributes;
|
|
40787
|
-
const assetType = attributes?.assetType ?? attributes?.asset_type;
|
|
40788
|
-
if (assetType !== AVANTIS_EQUITY_ASSET_TYPE) continue;
|
|
40789
|
-
equities.push({ key, pair });
|
|
40790
|
-
}
|
|
40791
|
-
if (equitiesGroupPairCount > 0 && equities.length === 0) {
|
|
40792
|
-
throw new Error(
|
|
40793
|
-
`avantis EQUITIES group has ${equitiesGroupPairCount} pairs but none matched the equity identity rule \u2014 upstream schema drift`
|
|
40794
|
-
);
|
|
40795
|
-
}
|
|
40796
|
-
const rows = [];
|
|
40797
|
-
for (const { key, pair } of equities) {
|
|
40798
|
-
const rawSymbol = pair.from;
|
|
40799
|
-
if (typeof rawSymbol !== "string" || rawSymbol.trim() === "") continue;
|
|
40800
|
-
const symbol = rawSymbol.toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
40801
|
-
if (symbol === "") continue;
|
|
40802
|
-
const marginFeeLong = parseFiniteNumber(pair.marginFee?.long);
|
|
40803
|
-
const marginFeeShort = parseFiniteNumber(pair.marginFee?.short);
|
|
40804
|
-
const borrowAprAnnual = avantisMarginFeeAnnualFraction(marginFeeLong);
|
|
40805
|
-
const attributes = pair.feed?.attributes;
|
|
40806
|
-
const feedId = pair.feed?.feedId;
|
|
40807
|
-
const quote = typeof pair.to === "string" ? pair.to : "USD";
|
|
40808
|
-
rows.push({
|
|
40809
|
-
venue: "avantis",
|
|
40810
|
-
market: `${symbol}/${quote}`,
|
|
40811
|
-
symbol,
|
|
40812
|
-
underlying: canonicalRwaUnderlying(symbol),
|
|
40813
|
-
asset_class: classifyRwaAssetClass(symbol),
|
|
40814
|
-
funding_mechanism: "funding_plus_borrow",
|
|
40815
|
-
funding_rate: null,
|
|
40816
|
-
funding_interval_hours: AVANTIS_QUOTE_INTERVAL_HOURS,
|
|
40817
|
-
funding_annualized: null,
|
|
40818
|
-
borrow_apr_annual: borrowAprAnnual,
|
|
40819
|
-
// Avantis publishes no prices; no Pyth integration exists here, so the
|
|
40820
|
-
// honest mark from the venue's own API is null.
|
|
40821
|
-
mark_price: null,
|
|
40822
|
-
oracle_price: null,
|
|
40823
|
-
// `pairOI` is ALREADY USD notional — do not multiply by price
|
|
40824
|
-
// (verified: summing pairOI across the group matches the venue's own
|
|
40825
|
-
// groupOI to the cent).
|
|
40826
|
-
open_interest_usd: parseFiniteNumber(pair.pairOI),
|
|
40827
|
-
// Rollover venue with no published 24h traded notional.
|
|
40828
|
-
volume_24h_usd: null,
|
|
40829
|
-
next_funding_time: null,
|
|
40830
|
-
is_delisted: false,
|
|
40831
|
-
is_price_suspect: false,
|
|
40832
|
-
price_suspect_basis: "insufficient_venues",
|
|
40833
|
-
price_deviation_pct: null,
|
|
40834
|
-
meta: {
|
|
40835
|
-
// The rollover is charged on COLLATERAL, not notional: ~10%/yr at
|
|
40836
|
-
// 5x max leverage is only ~2%/yr on notional. Not comparable to a
|
|
40837
|
-
// notional-based funding rate without this caveat.
|
|
40838
|
-
charged_on: "collateral",
|
|
40839
|
-
applies_to: "both_sides",
|
|
40840
|
-
pair_index: parseFiniteNumber(pair.index) ?? parseFiniteNumber(key),
|
|
40841
|
-
margin_fee_percent_per_hour: marginFeeLong,
|
|
40842
|
-
// Long and short match on all but one pair; when they diverge both
|
|
40843
|
-
// sides are recorded.
|
|
40844
|
-
margin_fee_short_percent_per_hour: marginFeeShort !== marginFeeLong ? marginFeeShort : null,
|
|
40845
|
-
max_leverage: parseFiniteNumber(pair.leverages?.maxLeverage),
|
|
40846
|
-
min_borrow_fee_annual_percent: parseFiniteNumber(
|
|
40847
|
-
pair.storagePairParams?.minBorrowFee
|
|
40848
|
-
),
|
|
40849
|
-
long_oi_usd: parseFiniteNumber(pair.openInterest?.long),
|
|
40850
|
-
short_oi_usd: parseFiniteNumber(pair.openInterest?.short),
|
|
40851
|
-
is_open: attributes?.isOpen ?? null,
|
|
40852
|
-
next_open: attributes?.nextOpen ?? null,
|
|
40853
|
-
schedule: attributes?.schedule ?? null,
|
|
40854
|
-
// For agents with their own oracle access (e.g. Pyth Hermes).
|
|
40855
|
-
pyth_feed_id: typeof feedId === "string" ? feedId : null,
|
|
40856
|
-
identified_by: "group-index+pyth-asset-type"
|
|
40857
|
-
}
|
|
40858
|
-
});
|
|
40859
|
-
}
|
|
40860
|
-
return rows;
|
|
40861
|
-
}
|
|
40862
|
-
|
|
40863
40777
|
// src/tools/rwa/venues/extended.ts
|
|
40864
40778
|
var EXTENDED_BASE = "https://api.starknet.extended.exchange/api/v1";
|
|
40865
40779
|
var EXTENDED_INTERVAL_HOURS = 1;
|
|
@@ -41177,89 +41091,9 @@ async function fetchLighterRows(fetchJson = defaultRwaFetchJson) {
|
|
|
41177
41091
|
return rows;
|
|
41178
41092
|
}
|
|
41179
41093
|
|
|
41180
|
-
// src/tools/rwa/venues/ostium.ts
|
|
41181
|
-
var OSTIUM_SUBGRAPH_URL = "https://api.subgraph.ormilabs.com/api/public/67a599d5-c8d2-4cc4-9c4d-2975a97bc5d8/subgraphs/ost-prod/live/gn";
|
|
41182
|
-
var OSTIUM_PRECISION_182 = 1e18;
|
|
41183
|
-
var OSTIUM_PAIRS_QUERY = "{ pairs(first: 300) { id from group { name } rolloverFeePerBlock lastTradePrice longOI shortOI } }";
|
|
41184
|
-
var GROUP_ASSET_CLASS = {
|
|
41185
|
-
stocks: "equity",
|
|
41186
|
-
indices: "index",
|
|
41187
|
-
commodities: "commodity",
|
|
41188
|
-
forex: "fx",
|
|
41189
|
-
etf: "etf",
|
|
41190
|
-
crypto: "crypto"
|
|
41191
|
-
};
|
|
41192
|
-
async function fetchOstiumRows(fetchJson = defaultRwaFetchJson) {
|
|
41193
|
-
const payload = await fetchJson(
|
|
41194
|
-
OSTIUM_SUBGRAPH_URL,
|
|
41195
|
-
{
|
|
41196
|
-
method: "POST",
|
|
41197
|
-
headers: { "Content-Type": "application/json" },
|
|
41198
|
-
body: JSON.stringify({ query: OSTIUM_PAIRS_QUERY })
|
|
41199
|
-
},
|
|
41200
|
-
"ostium subgraph pairs"
|
|
41201
|
-
);
|
|
41202
|
-
const pairs = payload?.data?.pairs;
|
|
41203
|
-
if (!Array.isArray(pairs)) {
|
|
41204
|
-
throw new Error("ostium subgraph returned unexpected shape");
|
|
41205
|
-
}
|
|
41206
|
-
const rows = [];
|
|
41207
|
-
for (const pair of pairs) {
|
|
41208
|
-
const symbol = pair.from?.toUpperCase();
|
|
41209
|
-
if (!symbol) continue;
|
|
41210
|
-
const groupName = pair.group?.name?.toLowerCase() ?? "";
|
|
41211
|
-
const venueClass = GROUP_ASSET_CLASS[groupName];
|
|
41212
|
-
const borrowAprAnnual = ostiumRolloverAnnualFraction(
|
|
41213
|
-
pair.rolloverFeePerBlock
|
|
41214
|
-
);
|
|
41215
|
-
if (borrowAprAnnual == null) continue;
|
|
41216
|
-
const markPrice = (() => {
|
|
41217
|
-
const scaled = parseFiniteNumber(pair.lastTradePrice);
|
|
41218
|
-
if (scaled == null) return null;
|
|
41219
|
-
const price = scaled / OSTIUM_PRECISION_182;
|
|
41220
|
-
return price > 0 && price < 1e6 ? price : null;
|
|
41221
|
-
})();
|
|
41222
|
-
const longUnits = parseFiniteNumber(pair.longOI);
|
|
41223
|
-
const shortUnits = parseFiniteNumber(pair.shortOI);
|
|
41224
|
-
const openInterestUsd = markPrice != null && longUnits != null && shortUnits != null ? (longUnits + shortUnits) / OSTIUM_PRECISION_182 * markPrice : null;
|
|
41225
|
-
rows.push({
|
|
41226
|
-
venue: "ostium",
|
|
41227
|
-
market: symbol,
|
|
41228
|
-
symbol,
|
|
41229
|
-
underlying: canonicalRwaUnderlying(symbol),
|
|
41230
|
-
asset_class: classifyRwaAssetClass(symbol, venueClass),
|
|
41231
|
-
funding_mechanism: "funding_plus_borrow",
|
|
41232
|
-
funding_rate: null,
|
|
41233
|
-
funding_interval_hours: 1,
|
|
41234
|
-
funding_annualized: null,
|
|
41235
|
-
borrow_apr_annual: borrowAprAnnual,
|
|
41236
|
-
mark_price: markPrice,
|
|
41237
|
-
oracle_price: null,
|
|
41238
|
-
open_interest_usd: openInterestUsd,
|
|
41239
|
-
// Ostium is a rollover venue with no published 24h traded notional.
|
|
41240
|
-
volume_24h_usd: null,
|
|
41241
|
-
next_funding_time: null,
|
|
41242
|
-
is_delisted: false,
|
|
41243
|
-
is_price_suspect: false,
|
|
41244
|
-
price_suspect_basis: "insufficient_venues",
|
|
41245
|
-
price_deviation_pct: null,
|
|
41246
|
-
meta: {
|
|
41247
|
-
pair_id: pair.id ?? null,
|
|
41248
|
-
group: groupName,
|
|
41249
|
-
rollover_fee_per_block_raw: pair.rolloverFeePerBlock ?? null,
|
|
41250
|
-
// Rollover accrues per block and is charged to BOTH sides.
|
|
41251
|
-
applies_to: "both_sides"
|
|
41252
|
-
}
|
|
41253
|
-
});
|
|
41254
|
-
}
|
|
41255
|
-
return rows;
|
|
41256
|
-
}
|
|
41257
|
-
|
|
41258
41094
|
// src/tools/rwa/venues/index.ts
|
|
41259
41095
|
var RWA_VENUE_IDS = [
|
|
41260
41096
|
"hyperliquid_dexes",
|
|
41261
|
-
"ostium",
|
|
41262
|
-
"avantis",
|
|
41263
41097
|
"lighter",
|
|
41264
41098
|
"extended"
|
|
41265
41099
|
];
|
|
@@ -41281,10 +41115,6 @@ async function fetchRwaVenueRows(venue, deps) {
|
|
|
41281
41115
|
note: noteParts.length > 0 ? noteParts.join(" | ") : void 0
|
|
41282
41116
|
};
|
|
41283
41117
|
}
|
|
41284
|
-
case "ostium":
|
|
41285
|
-
return { rows: await fetchOstiumRows(deps.fetchJson) };
|
|
41286
|
-
case "avantis":
|
|
41287
|
-
return { rows: await fetchAvantisRows(deps.fetchJson) };
|
|
41288
41118
|
case "lighter":
|
|
41289
41119
|
return { rows: await fetchLighterRows(deps.fetchJson) };
|
|
41290
41120
|
case "extended":
|
|
@@ -41421,7 +41251,7 @@ var FILTER_PARAM_PROPERTIES = {
|
|
|
41421
41251
|
},
|
|
41422
41252
|
venue: {
|
|
41423
41253
|
type: "string",
|
|
41424
|
-
description: 'Venue filter: "
|
|
41254
|
+
description: 'Venue filter: "lighter", "extended", "hyperliquid" (all builder dexes), or a specific builder dex like "hyperliquid:xyz".'
|
|
41425
41255
|
},
|
|
41426
41256
|
asset_class: {
|
|
41427
41257
|
type: "string",
|
|
@@ -41543,7 +41373,7 @@ function getAllRwaEndpoints() {
|
|
|
41543
41373
|
},
|
|
41544
41374
|
tool: {
|
|
41545
41375
|
name: "rwa_get_funding_rates",
|
|
41546
|
-
description: `Get current funding for tokenized real-world-asset perps (stocks, pre-IPO names, ETFs, indices, commodities, FX) normalized across all
|
|
41376
|
+
description: `Get current funding for tokenized real-world-asset perps (stocks, pre-IPO names, ETFs, indices, commodities, FX) normalized across all three RWA venues in ONE call: Hyperliquid HIP-3 builder dexes (each dex its own venue), Lighter, and Extended.
|
|
41547
41377
|
|
|
41548
41378
|
Venues are queried with per-venue error isolation: every venue reports its own runtime_status, so one failing venue never hides the others \u2014 partial results always flow.
|
|
41549
41379
|
|
|
@@ -41551,7 +41381,7 @@ ${ROW_CONTRACT}
|
|
|
41551
41381
|
|
|
41552
41382
|
Args:
|
|
41553
41383
|
- underlying (string, optional): filter to one canonical underlying (e.g. "TSLA").
|
|
41554
|
-
- venue (string, optional): filter to one venue ("
|
|
41384
|
+
- venue (string, optional): filter to one venue ("lighter", "extended", "hyperliquid", or "hyperliquid:xyz").
|
|
41555
41385
|
- asset_class (string, optional): filter to one asset class (equity, pre_ipo, etf, index, commodity, fx, crypto).
|
|
41556
41386
|
- limit (integer, optional): max rows (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}), deepest open interest first.
|
|
41557
41387
|
- offset (integer, optional): rows to skip; pass next_offset to page.
|
|
@@ -41616,7 +41446,7 @@ Don't use when:
|
|
|
41616
41446
|
},
|
|
41617
41447
|
tool: {
|
|
41618
41448
|
name: "rwa_get_markets",
|
|
41619
|
-
description: `Get the tokenized real-world-asset perp UNIVERSE across all
|
|
41449
|
+
description: `Get the tokenized real-world-asset perp UNIVERSE across all three RWA venues (Hyperliquid HIP-3 builder dexes, Lighter, Extended): every listed market with asset_class, open interest where the venue exposes it, mark/oracle price, and the delisted/price-suspect flags. Funding columns are omitted \u2014 use rwa_get_funding_rates for carry.
|
|
41620
41450
|
|
|
41621
41451
|
Venues are queried with per-venue error isolation (per-venue runtime_status; partial results always flow).
|
|
41622
41452
|
|
|
@@ -41691,7 +41521,7 @@ Don't use when:
|
|
|
41691
41521
|
returnsNote: 'A "note" field reports partial dex failures. Delisted HIP-3 markets ship with is_delisted: true and a FROZEN mark price \u2014 never trust their price.',
|
|
41692
41522
|
useWhen: [
|
|
41693
41523
|
"You want the full HIP-3 equity/pre-IPO perp landscape in one normalized call",
|
|
41694
|
-
"You are checking builder-set funding on a specific dex (xyz,
|
|
41524
|
+
"You are checking builder-set funding on a specific dex (xyz, flx, cash, ...)"
|
|
41695
41525
|
],
|
|
41696
41526
|
dontUseWhen: [
|
|
41697
41527
|
"You need main-dex Hyperliquid crypto perps (use hyperliquid_get_meta / hyperliquid_get_perp_volume_leaderboard)",
|
|
@@ -41699,38 +41529,6 @@ Don't use when:
|
|
|
41699
41529
|
],
|
|
41700
41530
|
deps
|
|
41701
41531
|
}),
|
|
41702
|
-
venueRawEndpoint({
|
|
41703
|
-
toolName: "rwa_get_ostium_markets",
|
|
41704
|
-
resource: "rwa-ostium-markets",
|
|
41705
|
-
venueId: "ostium",
|
|
41706
|
-
tags: ["rwa", "ostium", "equity-perps", "rollover", "commodities", "fx"],
|
|
41707
|
-
summary: `Get Ostium's RWA perp markets (stocks, indices, commodities, FX; crypto pairs stamped "crypto") from the official public subgraph. Ostium charges NO long/short funding: the holding cost is a carry-anchored rollover accrued per Arbitrum block and charged to BOTH sides, so rows carry funding_mechanism "funding_plus_borrow" with the cost in borrow_apr_annual and funding_rate null.`,
|
|
41708
|
-
returnsNote: "borrow_apr_annual is annualized from the on-chain per-block rollover (PRECISION_18, 250ms Arbitrum blocks). It is a both-sides cost \u2014 never compare its sign against a funding transfer.",
|
|
41709
|
-
useWhen: [
|
|
41710
|
-
"You need Ostium's rollover (holding cost) for stock/commodity/FX pairs",
|
|
41711
|
-
"You are auditing the raw per-block rollover behind the annualized number"
|
|
41712
|
-
],
|
|
41713
|
-
dontUseWhen: [
|
|
41714
|
-
"You want a cross-venue funding comparison (use rwa_get_funding_rates \u2014 Ostium's both-sides rollover is excluded from funding spreads by design)"
|
|
41715
|
-
],
|
|
41716
|
-
deps
|
|
41717
|
-
}),
|
|
41718
|
-
venueRawEndpoint({
|
|
41719
|
-
toolName: "rwa_get_avantis_markets",
|
|
41720
|
-
resource: "rwa-avantis-markets",
|
|
41721
|
-
venueId: "avantis",
|
|
41722
|
-
tags: ["rwa", "avantis", "equity-perps", "rollover", "open-interest"],
|
|
41723
|
-
summary: `Get Avantis's equity perp markets (Base) from the official public data API: listing, USD open interest, both-sides rollover fees, and market hours. Avantis has NO funding and publishes NO prices (pairs name a Pyth feed instead), so rows carry funding_mechanism "funding_plus_borrow", the rollover in borrow_apr_annual, and mark_price null with the pyth_feed_id in meta.`,
|
|
41724
|
-
returnsNote: "The rollover is charged on COLLATERAL, not notional (meta.charged_on) \u2014 at 5x max leverage a 10%/yr collateral charge is ~2%/yr on notional. open_interest_usd comes from the venue's own USD pairOI field.",
|
|
41725
|
-
useWhen: [
|
|
41726
|
-
"You need Avantis equity-perp open interest, fees, or market hours",
|
|
41727
|
-
"You have your own Pyth access and want the feed ids to price these pairs"
|
|
41728
|
-
],
|
|
41729
|
-
dontUseWhen: [
|
|
41730
|
-
"You need a mark price from this venue (Avantis publishes none; cross-venue marks live in rwa_get_funding_rates rows from other venues)"
|
|
41731
|
-
],
|
|
41732
|
-
deps
|
|
41733
|
-
}),
|
|
41734
41532
|
venueRawEndpoint({
|
|
41735
41533
|
toolName: "rwa_get_lighter_markets",
|
|
41736
41534
|
resource: "rwa-lighter-markets",
|
|
@@ -41767,55 +41565,51 @@ Don't use when:
|
|
|
41767
41565
|
}
|
|
41768
41566
|
|
|
41769
41567
|
// src/tools/archive/client.ts
|
|
41770
|
-
|
|
41771
|
-
var
|
|
41772
|
-
var
|
|
41773
|
-
var
|
|
41774
|
-
var
|
|
41568
|
+
var ARCHIVE_URL_ENV = "CLICKHOUSE_URL";
|
|
41569
|
+
var ARCHIVE_PASSWORD_ENV = "CLICKHOUSE_READ_PASSWORD";
|
|
41570
|
+
var ARCHIVE_USER = "hive_read";
|
|
41571
|
+
var REQUEST_TIMEOUT_MS = 2e4;
|
|
41572
|
+
var ARCHIVE_MISSING_CREDENTIALS_MESSAGE = `Hive Archive credentials not configured \u2014 set ${ARCHIVE_URL_ENV} and ${ARCHIVE_PASSWORD_ENV} to enable archive_ history tools. Until then use the live derivatives tools (ccxt_fetch_funding_rates_multi, hyperliquid_get_funding_history) for current data.`;
|
|
41775
41573
|
function isArchiveConfigured() {
|
|
41776
|
-
return hasEnvVars(ARCHIVE_URL_ENV,
|
|
41574
|
+
return hasEnvVars(ARCHIVE_URL_ENV, ARCHIVE_PASSWORD_ENV);
|
|
41777
41575
|
}
|
|
41778
41576
|
function getArchiveClient() {
|
|
41779
41577
|
if (!isArchiveConfigured()) return null;
|
|
41780
|
-
|
|
41781
|
-
|
|
41782
|
-
|
|
41783
|
-
|
|
41784
|
-
|
|
41785
|
-
);
|
|
41786
|
-
|
|
41787
|
-
|
|
41578
|
+
const base = getEnvVar(ARCHIVE_URL_ENV);
|
|
41579
|
+
const auth = Buffer.from(
|
|
41580
|
+
`${ARCHIVE_USER}:${getEnvVar(ARCHIVE_PASSWORD_ENV)}`
|
|
41581
|
+
).toString("base64");
|
|
41582
|
+
return async (sql, params = {}) => {
|
|
41583
|
+
const url = new URL(base);
|
|
41584
|
+
url.searchParams.set("date_time_output_format", "iso");
|
|
41585
|
+
for (const [name, value] of Object.entries(params)) {
|
|
41586
|
+
url.searchParams.set(`param_${name}`, String(value));
|
|
41587
|
+
}
|
|
41588
|
+
const response = await fetch(url, {
|
|
41589
|
+
method: "POST",
|
|
41590
|
+
headers: { Authorization: `Basic ${auth}` },
|
|
41591
|
+
body: `${sql} FORMAT JSONEachRow`,
|
|
41592
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
41593
|
+
});
|
|
41594
|
+
const body = await response.text();
|
|
41595
|
+
if (!response.ok) {
|
|
41596
|
+
throw new Error(`ClickHouse ${response.status}: ${body.slice(0, 500)}`);
|
|
41597
|
+
}
|
|
41598
|
+
return body.split("\n").filter((line) => line !== "").map((line) => JSON.parse(line));
|
|
41599
|
+
};
|
|
41788
41600
|
}
|
|
41789
41601
|
|
|
41790
41602
|
// src/tools/archive/api.ts
|
|
41791
41603
|
var DEFAULT_LIMIT2 = 100;
|
|
41792
41604
|
var MAX_LIMIT2 = 500;
|
|
41793
|
-
var
|
|
41794
|
-
funding: "2023-11",
|
|
41795
|
-
open_interest: "2025-04",
|
|
41796
|
-
basis: "2025-03",
|
|
41797
|
-
long_short: "2026-03",
|
|
41798
|
-
liquidations: "2025-04"
|
|
41799
|
-
};
|
|
41800
|
-
var ARCHIVE_SINCE_NOTE = `archive since ${ARCHIVE_SINCE.funding}`;
|
|
41801
|
-
var QUERY_TIMEOUT_MS = 1e4;
|
|
41802
|
-
var VENUE_SCAN_MAX_STEPS = 64;
|
|
41605
|
+
var VENUE_LIST_MAX = 64;
|
|
41803
41606
|
var COVERAGE_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
41804
41607
|
var COVERAGE_CACHE_MAX_ENTRIES = 16;
|
|
41805
|
-
var HOURS_PER_YEAR2 = 8760;
|
|
41806
|
-
function annualizeRate(rate, intervalHours) {
|
|
41807
|
-
if (!Number.isFinite(rate)) return null;
|
|
41808
|
-
if (!Number.isFinite(intervalHours) || intervalHours <= 0) return null;
|
|
41809
|
-
return rate * (HOURS_PER_YEAR2 / intervalHours);
|
|
41810
|
-
}
|
|
41811
41608
|
function clampLimit3(value) {
|
|
41812
41609
|
const parsed = typeof value === "number" ? Math.floor(value) : NaN;
|
|
41813
41610
|
if (!Number.isFinite(parsed)) return DEFAULT_LIMIT2;
|
|
41814
41611
|
return Math.min(Math.max(parsed, 1), MAX_LIMIT2);
|
|
41815
41612
|
}
|
|
41816
|
-
function escapeLikeLiteral(value) {
|
|
41817
|
-
return value.replace(/[\\%_]/g, (match) => `\\${match}`);
|
|
41818
|
-
}
|
|
41819
41613
|
var ArchiveInputError = class extends Error {
|
|
41820
41614
|
};
|
|
41821
41615
|
function normalizeBaseCoin(value) {
|
|
@@ -41858,96 +41652,129 @@ function parseHistoryArgs(args) {
|
|
|
41858
41652
|
};
|
|
41859
41653
|
}
|
|
41860
41654
|
function archiveQueryError(context, error) {
|
|
41861
|
-
const detail = error.message
|
|
41862
|
-
|
|
41863
|
-
if (error.code === "57014" || /timeout/i.test(detail)) {
|
|
41655
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
41656
|
+
if (/Code: (158|159|160|396)\b|TimeoutError|aborted|timeout/i.test(detail) || error instanceof Error && error.name === "TimeoutError") {
|
|
41864
41657
|
return new Error(
|
|
41865
|
-
`Hive Archive query timed out while ${context}
|
|
41658
|
+
`Hive Archive query timed out or hit its read limit while ${context}. Narrow the time range with from/to or lower limit, then retry.`
|
|
41866
41659
|
);
|
|
41867
41660
|
}
|
|
41868
|
-
if (/
|
|
41661
|
+
if (/ClickHouse (401|403)\b|Code: 516\b|AUTHENTICATION_FAILED/.test(detail)) {
|
|
41869
41662
|
return new Error(
|
|
41870
|
-
`Hive Archive rejected the configured credentials while ${context}
|
|
41663
|
+
`Hive Archive rejected the configured credentials while ${context}. The archive password needs rotation \u2014 this is a server configuration issue, not a query problem.`
|
|
41871
41664
|
);
|
|
41872
41665
|
}
|
|
41873
41666
|
return new Error(
|
|
41874
|
-
`Hive Archive query failed while ${context}
|
|
41667
|
+
`Hive Archive query failed while ${context}: ${detail}. Retry once; if it persists the archive is degraded \u2014 use live derivatives tools instead.`
|
|
41875
41668
|
);
|
|
41876
41669
|
}
|
|
41877
41670
|
var HISTORY_TABLES = {
|
|
41878
41671
|
funding: {
|
|
41879
|
-
table: "
|
|
41880
|
-
columns: "exchange,symbol,base_coin,rate,interval_hours,settled_at",
|
|
41881
|
-
tsColumn: "settled_at"
|
|
41672
|
+
table: "core.funding_settlements",
|
|
41673
|
+
columns: "venue AS exchange, venue_symbol AS symbol, base_coin, rate, interval_hours, settled_at, apr AS funding_annualized",
|
|
41674
|
+
tsColumn: "settled_at",
|
|
41675
|
+
numeric: ["rate", "funding_annualized"]
|
|
41882
41676
|
},
|
|
41883
41677
|
open_interest: {
|
|
41884
|
-
table: "
|
|
41885
|
-
columns: "exchange,base_coin,symbol,open_interest_value,timestamp",
|
|
41886
|
-
tsColumn: "
|
|
41678
|
+
table: "core.open_interest_hourly",
|
|
41679
|
+
columns: "venue AS exchange, base_coin, venue_symbol AS symbol, oi_usd AS open_interest_value, hour AS timestamp",
|
|
41680
|
+
tsColumn: "hour",
|
|
41681
|
+
numeric: ["open_interest_value"]
|
|
41887
41682
|
},
|
|
41888
41683
|
basis: {
|
|
41889
|
-
table: "
|
|
41890
|
-
columns: "exchange,base_coin,spot_price,futures_price,basis_rate,
|
|
41891
|
-
tsColumn: "
|
|
41684
|
+
table: "core.basis",
|
|
41685
|
+
columns: "venue AS exchange, base_coin, spot_price, futures_price, basis_rate, basis_pct, ts AS timestamp",
|
|
41686
|
+
tsColumn: "ts",
|
|
41687
|
+
numeric: ["spot_price", "futures_price", "basis_rate", "basis_pct"]
|
|
41892
41688
|
},
|
|
41893
41689
|
long_short: {
|
|
41894
|
-
table: "
|
|
41895
|
-
columns: "exchange,base_coin,symbol,long_ratio,short_ratio,timestamp",
|
|
41896
|
-
tsColumn: "
|
|
41690
|
+
table: "core.long_short_ratio",
|
|
41691
|
+
columns: "venue AS exchange, base_coin, venue_symbol AS symbol, ratio_kind, long_ratio, short_ratio, ts AS timestamp",
|
|
41692
|
+
tsColumn: "ts",
|
|
41693
|
+
numeric: ["long_ratio", "short_ratio"]
|
|
41897
41694
|
},
|
|
41898
41695
|
liquidations: {
|
|
41899
|
-
table: "
|
|
41900
|
-
columns: "exchange,base_coin,symbol,side,quantity,price,timestamp",
|
|
41901
|
-
tsColumn: "
|
|
41696
|
+
table: "core.liquidations",
|
|
41697
|
+
columns: "venue AS exchange, base_coin, venue_symbol AS symbol, side, qty_base AS quantity, price, notional_usd, event_time AS timestamp",
|
|
41698
|
+
tsColumn: "event_time",
|
|
41699
|
+
numeric: ["quantity", "price", "notional_usd"]
|
|
41902
41700
|
}
|
|
41903
41701
|
};
|
|
41904
|
-
|
|
41702
|
+
var TOP_TRADER_SUFFIX = /_top_trader$/i;
|
|
41703
|
+
function toNumbers(row, keys) {
|
|
41704
|
+
const out = { ...row };
|
|
41705
|
+
for (const key of keys) {
|
|
41706
|
+
const value = out[key];
|
|
41707
|
+
if (typeof value === "string") out[key] = Number(value);
|
|
41708
|
+
}
|
|
41709
|
+
return out;
|
|
41710
|
+
}
|
|
41711
|
+
async function fetchHistoryRows(query, dataset, filters) {
|
|
41905
41712
|
const spec = HISTORY_TABLES[dataset];
|
|
41906
|
-
|
|
41713
|
+
const where = ["base_coin = {base_coin:String}"];
|
|
41714
|
+
const params = { base_coin: filters.base_coin };
|
|
41907
41715
|
if (filters.exchange) {
|
|
41908
|
-
|
|
41716
|
+
let venue = filters.exchange;
|
|
41717
|
+
if (dataset === "long_short" && TOP_TRADER_SUFFIX.test(venue)) {
|
|
41718
|
+
venue = venue.replace(TOP_TRADER_SUFFIX, "");
|
|
41719
|
+
where.push("ratio_kind != 'accounts'");
|
|
41720
|
+
}
|
|
41721
|
+
where.push("lower(venue) = lower({exchange:String})");
|
|
41722
|
+
params.exchange = venue;
|
|
41909
41723
|
}
|
|
41910
|
-
if (filters.from)
|
|
41911
|
-
|
|
41912
|
-
|
|
41913
|
-
|
|
41724
|
+
if (filters.from) {
|
|
41725
|
+
where.push(`${spec.tsColumn} >= fromUnixTimestamp64Milli({from_ms:Int64})`);
|
|
41726
|
+
params.from_ms = Date.parse(filters.from);
|
|
41727
|
+
}
|
|
41728
|
+
if (filters.to) {
|
|
41729
|
+
where.push(`${spec.tsColumn} <= fromUnixTimestamp64Milli({to_ms:Int64})`);
|
|
41730
|
+
params.to_ms = Date.parse(filters.to);
|
|
41731
|
+
}
|
|
41732
|
+
const sql = `SELECT ${spec.columns} FROM ${spec.table} FINAL WHERE ${where.join(" AND ")} ORDER BY ${spec.tsColumn} DESC LIMIT ${filters.limit}`;
|
|
41733
|
+
try {
|
|
41734
|
+
const rows = await query(sql, params);
|
|
41735
|
+
return rows.map((row) => toNumbers(row, spec.numeric));
|
|
41736
|
+
} catch (error) {
|
|
41914
41737
|
throw archiveQueryError(
|
|
41915
41738
|
`reading ${spec.table} for ${filters.base_coin}`,
|
|
41916
41739
|
error
|
|
41917
41740
|
);
|
|
41918
41741
|
}
|
|
41919
|
-
return data ?? [];
|
|
41920
41742
|
}
|
|
41921
41743
|
var coverageCache = /* @__PURE__ */ new Map();
|
|
41922
|
-
async function
|
|
41923
|
-
|
|
41924
|
-
|
|
41925
|
-
|
|
41926
|
-
|
|
41744
|
+
async function probeExtremes(query, dataset, baseCoin) {
|
|
41745
|
+
const spec = HISTORY_TABLES[dataset];
|
|
41746
|
+
const where = baseCoin ? " WHERE base_coin = {base_coin:String}" : "";
|
|
41747
|
+
try {
|
|
41748
|
+
const [row] = await query(
|
|
41749
|
+
`SELECT minOrNull(${spec.tsColumn}) AS earliest_at, maxOrNull(${spec.tsColumn}) AS latest_at FROM ${spec.table} FINAL${where}`,
|
|
41750
|
+
baseCoin ? { base_coin: baseCoin } : {}
|
|
41751
|
+
);
|
|
41752
|
+
return {
|
|
41753
|
+
dataset,
|
|
41754
|
+
table: spec.table,
|
|
41755
|
+
scope: baseCoin ? "probe_coin" : "all_coins",
|
|
41756
|
+
earliest_at: typeof row?.earliest_at === "string" ? row.earliest_at : null,
|
|
41757
|
+
latest_at: typeof row?.latest_at === "string" ? row.latest_at : null
|
|
41758
|
+
};
|
|
41759
|
+
} catch (error) {
|
|
41927
41760
|
throw archiveQueryError(`probing ${spec.table} coverage`, error);
|
|
41928
41761
|
}
|
|
41929
|
-
const row = (data ?? [])[0];
|
|
41930
|
-
const value = row?.[spec.tsColumn];
|
|
41931
|
-
return typeof value === "string" ? value : null;
|
|
41932
41762
|
}
|
|
41933
|
-
async function listFundingVenues(
|
|
41934
|
-
|
|
41935
|
-
|
|
41936
|
-
|
|
41937
|
-
|
|
41938
|
-
|
|
41939
|
-
|
|
41940
|
-
|
|
41941
|
-
|
|
41942
|
-
|
|
41943
|
-
|
|
41944
|
-
|
|
41945
|
-
venues.push(row.exchange);
|
|
41946
|
-
cursor = row.exchange;
|
|
41763
|
+
async function listFundingVenues(query) {
|
|
41764
|
+
try {
|
|
41765
|
+
const rows = await query(
|
|
41766
|
+
`SELECT DISTINCT venue FROM ${HISTORY_TABLES.funding.table} FINAL ORDER BY venue LIMIT ${VENUE_LIST_MAX + 1}`
|
|
41767
|
+
);
|
|
41768
|
+
const venues = rows.map((row) => row.venue).filter((venue) => typeof venue === "string");
|
|
41769
|
+
return {
|
|
41770
|
+
venues: venues.slice(0, VENUE_LIST_MAX),
|
|
41771
|
+
truncated: venues.length > VENUE_LIST_MAX
|
|
41772
|
+
};
|
|
41773
|
+
} catch (error) {
|
|
41774
|
+
throw archiveQueryError("enumerating archive venues", error);
|
|
41947
41775
|
}
|
|
41948
|
-
return { venues, truncated: true };
|
|
41949
41776
|
}
|
|
41950
|
-
async function fetchCoverage(
|
|
41777
|
+
async function fetchCoverage(query, probeBaseCoin) {
|
|
41951
41778
|
const cached = coverageCache.get(probeBaseCoin);
|
|
41952
41779
|
if (cached && Date.now() - cached.at < COVERAGE_CACHE_TTL_MS) {
|
|
41953
41780
|
return { coverage: cached.payload, cache: "hit" };
|
|
@@ -41958,40 +41785,20 @@ async function fetchCoverage(client2, probeBaseCoin) {
|
|
|
41958
41785
|
"long_short",
|
|
41959
41786
|
"liquidations"
|
|
41960
41787
|
];
|
|
41961
|
-
const [venueScan,
|
|
41962
|
-
listFundingVenues(
|
|
41963
|
-
|
|
41964
|
-
|
|
41965
|
-
|
|
41966
|
-
|
|
41967
|
-
probeTimestamp(client2, HISTORY_TABLES[dataset], "desc", probeBaseCoin)
|
|
41968
|
-
])
|
|
41788
|
+
const [venueScan, funding, ...futures] = await Promise.all([
|
|
41789
|
+
listFundingVenues(query),
|
|
41790
|
+
probeExtremes(query, "funding", null),
|
|
41791
|
+
...futuresDatasets.map(
|
|
41792
|
+
(dataset) => probeExtremes(query, dataset, probeBaseCoin)
|
|
41793
|
+
)
|
|
41969
41794
|
]);
|
|
41970
|
-
const datasets = [
|
|
41971
|
-
{
|
|
41972
|
-
dataset: "funding",
|
|
41973
|
-
table: HISTORY_TABLES.funding.table,
|
|
41974
|
-
scope: "all_coins",
|
|
41975
|
-
earliest_at: fundingEarliest,
|
|
41976
|
-
latest_at: fundingLatest
|
|
41977
|
-
},
|
|
41978
|
-
...futuresDatasets.map((dataset, index) => {
|
|
41979
|
-
return {
|
|
41980
|
-
dataset,
|
|
41981
|
-
table: HISTORY_TABLES[dataset].table,
|
|
41982
|
-
scope: "probe_coin",
|
|
41983
|
-
earliest_at: futuresExtremes[index * 2],
|
|
41984
|
-
latest_at: futuresExtremes[index * 2 + 1]
|
|
41985
|
-
};
|
|
41986
|
-
})
|
|
41987
|
-
];
|
|
41988
41795
|
const coverage = {
|
|
41989
41796
|
venues: venueScan.venues,
|
|
41990
41797
|
venue_count: venueScan.venues.length,
|
|
41991
41798
|
venues_truncated: venueScan.truncated,
|
|
41992
41799
|
probe_base_coin: probeBaseCoin,
|
|
41993
|
-
datasets,
|
|
41994
|
-
note:
|
|
41800
|
+
datasets: [funding, ...futures],
|
|
41801
|
+
note: `Funding extremes cover all coins; the other datasets are probed for probe_base_coin \u2014 pass base_coin to probe another asset. Venues are the distinct funding venues in the archive. Older history is still being loaded, so earliest_at can move back over time.`
|
|
41995
41802
|
};
|
|
41996
41803
|
if (coverageCache.size >= COVERAGE_CACHE_MAX_ENTRIES) {
|
|
41997
41804
|
const oldestKey = coverageCache.keys().next().value;
|
|
@@ -42077,21 +41884,21 @@ function missingCredentialsResult(tool101) {
|
|
|
42077
41884
|
true
|
|
42078
41885
|
);
|
|
42079
41886
|
}
|
|
41887
|
+
var HISTORY_NOTE = "History depth varies by dataset and venue and older history is still being loaded \u2014 call archive_get_coverage for the earliest and latest rows.";
|
|
42080
41888
|
function historyHandler(toolName, dataset, options) {
|
|
42081
41889
|
return async (_client, args) => {
|
|
42082
|
-
const
|
|
42083
|
-
if (!
|
|
41890
|
+
const query = getArchiveClient();
|
|
41891
|
+
if (!query) return missingCredentialsResult(toolName);
|
|
42084
41892
|
try {
|
|
42085
41893
|
const filters = parseHistoryArgs(args);
|
|
42086
|
-
const
|
|
42087
|
-
const mapped = options?.mapRow ? rows.map(options.mapRow) : rows;
|
|
41894
|
+
const mapped = await fetchHistoryRows(query, dataset, filters);
|
|
42088
41895
|
const spec = HISTORY_TABLES[dataset];
|
|
42089
41896
|
return asTextContentResult({
|
|
42090
41897
|
provider: PROVIDER_NAME,
|
|
42091
41898
|
dataset: spec.table,
|
|
42092
41899
|
runtime_status: "ok",
|
|
42093
41900
|
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42094
|
-
note: options?.extraNote ?
|
|
41901
|
+
note: options?.extraNote ? `${options.extraNote} ${HISTORY_NOTE}` : HISTORY_NOTE,
|
|
42095
41902
|
filters,
|
|
42096
41903
|
order: `${spec.tsColumn} desc`,
|
|
42097
41904
|
returnedCount: mapped.length,
|
|
@@ -42113,7 +41920,7 @@ function getAllArchiveEndpoints() {
|
|
|
42113
41920
|
},
|
|
42114
41921
|
tool: {
|
|
42115
41922
|
name: "archive_get_funding_history",
|
|
42116
|
-
description: `Get historical perp funding-rate SETTLEMENTS for one base coin across up to
|
|
41923
|
+
description: `Get historical perp funding-rate SETTLEMENTS for one base coin across up to 35 CEX+DEX venues from the Hive Archive. Each row is one settled funding event: exchange, symbol, base_coin, rate (per-interval fraction, positive = longs pay shorts), interval_hours, settled_at (end of the interval, UTC) \u2014 plus funding_annualized, the simple annual rate from that row's own interval (rate * 8760 / interval_hours, never a guessed 8h default). Call archive_get_coverage for how far back the history reaches.
|
|
42117
41924
|
|
|
42118
41925
|
${SHARED_ARGS_DOC}
|
|
42119
41926
|
|
|
@@ -42134,19 +41941,11 @@ Don't use when:
|
|
|
42134
41941
|
- You need one venue's own raw history endpoint (use get_funding_rate_history or hyperliquid_get_funding_history)
|
|
42135
41942
|
- You need tokenized-stock/RWA perp funding (use rwa_get_funding_rates)`,
|
|
42136
41943
|
inputSchema: historyInputSchema(
|
|
42137
|
-
'Base asset ticker to fetch settlements for, e.g. "BTC". Required
|
|
41944
|
+
'Base asset ticker to fetch settlements for, e.g. "BTC". Required.'
|
|
42138
41945
|
),
|
|
42139
41946
|
annotations: ARCHIVE_READ_ANNOTATIONS
|
|
42140
41947
|
},
|
|
42141
|
-
handler: historyHandler("archive_get_funding_history", "funding"
|
|
42142
|
-
mapRow: (row) => ({
|
|
42143
|
-
...row,
|
|
42144
|
-
funding_annualized: annualizeRate(
|
|
42145
|
-
Number(row.rate),
|
|
42146
|
-
Number(row.interval_hours)
|
|
42147
|
-
)
|
|
42148
|
-
})
|
|
42149
|
-
})
|
|
41948
|
+
handler: historyHandler("archive_get_funding_history", "funding")
|
|
42150
41949
|
},
|
|
42151
41950
|
{
|
|
42152
41951
|
metadata: {
|
|
@@ -42162,7 +41961,7 @@ Don't use when:
|
|
|
42162
41961
|
},
|
|
42163
41962
|
tool: {
|
|
42164
41963
|
name: "archive_get_open_interest_history",
|
|
42165
|
-
description: `Get historical perp open-interest snapshots for one base coin across venues from the Hive Archive
|
|
41964
|
+
description: `Get historical perp open-interest snapshots for one base coin across venues from the Hive Archive. Each row: exchange, base_coin, symbol (venue-native contract), open_interest_value (open interest of that contract in US dollars, one side, at the venue mark price), timestamp (start of the hour; the value is the hour close). Call archive_get_coverage for exact per-dataset depth.
|
|
42166
41965
|
|
|
42167
41966
|
${SHARED_ARGS_DOC}
|
|
42168
41967
|
|
|
@@ -42189,7 +41988,7 @@ Don't use when:
|
|
|
42189
41988
|
"archive_get_open_interest_history",
|
|
42190
41989
|
"open_interest",
|
|
42191
41990
|
{
|
|
42192
|
-
extraNote: "open_interest_value is
|
|
41991
|
+
extraNote: "open_interest_value is US dollars, one side counted, per contract."
|
|
42193
41992
|
}
|
|
42194
41993
|
)
|
|
42195
41994
|
},
|
|
@@ -42201,7 +42000,7 @@ Don't use when:
|
|
|
42201
42000
|
},
|
|
42202
42001
|
tool: {
|
|
42203
42002
|
name: "archive_get_basis_history",
|
|
42204
|
-
description: `Get historical spot-vs-futures BASIS snapshots for one base coin across venues from the Hive Archive. Each row: exchange, base_coin, spot_price, futures_price, basis_rate (fraction),
|
|
42003
|
+
description: `Get historical spot-vs-futures BASIS snapshots for one base coin across venues from the Hive Archive. Each row: exchange, base_coin, spot_price, futures_price, basis_rate ((futures - spot) / spot as a fraction), basis_pct (100 * basis_rate, not annualised), timestamp \u2014 computed at collection time from the venue's own spot and perpetual prices.
|
|
42205
42004
|
|
|
42206
42005
|
${SHARED_ARGS_DOC}
|
|
42207
42006
|
|
|
@@ -42240,7 +42039,7 @@ Don't use when:
|
|
|
42240
42039
|
},
|
|
42241
42040
|
tool: {
|
|
42242
42041
|
name: "archive_get_long_short_history",
|
|
42243
|
-
description: `Get historical long/short-ratio snapshots for one base coin from the Hive Archive. Each row: exchange, base_coin, symbol, long_ratio, short_ratio (fractions summing to ~1), timestamp.
|
|
42042
|
+
description: `Get historical long/short-ratio snapshots for one base coin from the Hive Archive. Each row: exchange, base_coin, symbol, ratio_kind ("accounts" = all accounts, "top_trader_accounts", "top_trader_positions"), long_ratio, short_ratio (fractions summing to ~1), timestamp. Filter exchange "Binance" for every Binance series, or "Binance_top_trader" for its top-trader series only.
|
|
42244
42043
|
|
|
42245
42044
|
${SHARED_ARGS_DOC}
|
|
42246
42045
|
|
|
@@ -42273,7 +42072,7 @@ Don't use when:
|
|
|
42273
42072
|
},
|
|
42274
42073
|
tool: {
|
|
42275
42074
|
name: "archive_get_liquidations_history",
|
|
42276
|
-
description: `Get historical liquidation events for one base coin from the Hive Archive. Each row: exchange, base_coin, symbol, side ("long" = longs liquidated, "short" = shorts liquidated), quantity
|
|
42075
|
+
description: `Get historical liquidation events for one base coin from the Hive Archive. Each row: exchange, base_coin, symbol, side ("long" = longs liquidated, "short" = shorts liquidated), quantity (base coins), price (quote per base coin), notional_usd (US dollars), timestamp.
|
|
42277
42076
|
|
|
42278
42077
|
${SHARED_ARGS_DOC}
|
|
42279
42078
|
|
|
@@ -42308,7 +42107,7 @@ Don't use when:
|
|
|
42308
42107
|
},
|
|
42309
42108
|
tool: {
|
|
42310
42109
|
name: "archive_get_coverage",
|
|
42311
|
-
description: `Discover what the Hive Archive holds: the
|
|
42110
|
+
description: `Discover what the Hive Archive holds: the venue list (up to 35 CEX+DEX funding venues) and earliest/latest timestamps per dataset (funding settlements, open interest, basis, long/short ratio, liquidations). Funding extremes are global; the other datasets are probed for one base coin (default "BTC"). Results are cached in-process for 10 minutes.
|
|
42312
42111
|
|
|
42313
42112
|
Args:
|
|
42314
42113
|
- base_coin (string, optional): probe coin for the futures datasets (default "BTC"). Pass another ticker to check archive depth for that asset.
|
|
@@ -42340,11 +42139,11 @@ Don't use when:
|
|
|
42340
42139
|
},
|
|
42341
42140
|
handler: async (_client, args) => {
|
|
42342
42141
|
const toolName = "archive_get_coverage";
|
|
42343
|
-
const
|
|
42344
|
-
if (!
|
|
42142
|
+
const query = getArchiveClient();
|
|
42143
|
+
if (!query) return missingCredentialsResult(toolName);
|
|
42345
42144
|
try {
|
|
42346
42145
|
const probe = typeof args?.base_coin === "string" && args.base_coin.trim() !== "" ? args.base_coin.trim().toUpperCase() : "BTC";
|
|
42347
|
-
const { coverage, cache } = await fetchCoverage(
|
|
42146
|
+
const { coverage, cache } = await fetchCoverage(query, probe);
|
|
42348
42147
|
return asTextContentResult({
|
|
42349
42148
|
provider: PROVIDER_NAME,
|
|
42350
42149
|
runtime_status: "ok",
|
|
@@ -42375,7 +42174,7 @@ var PolymarketNotFoundError = class extends Error {
|
|
|
42375
42174
|
}
|
|
42376
42175
|
url;
|
|
42377
42176
|
};
|
|
42378
|
-
function
|
|
42177
|
+
function buildUrl2(base, path, params = {}) {
|
|
42379
42178
|
const url = new URL(path, base);
|
|
42380
42179
|
for (const [key, value] of Object.entries(params)) {
|
|
42381
42180
|
if (value === void 0) continue;
|
|
@@ -43014,7 +42813,7 @@ function toActivityRow(raw) {
|
|
|
43014
42813
|
}
|
|
43015
42814
|
async function fetchGammaKeyset(resource, params) {
|
|
43016
42815
|
const label = `Gamma /${resource}/keyset`;
|
|
43017
|
-
const url =
|
|
42816
|
+
const url = buildUrl2(GAMMA_BASE_URL, `/${resource}/keyset`, params);
|
|
43018
42817
|
const page = asRecord(await fetchPolymarketJson(url, label));
|
|
43019
42818
|
return {
|
|
43020
42819
|
url,
|
|
@@ -43042,7 +42841,7 @@ function parseDataPage(value, label) {
|
|
|
43042
42841
|
};
|
|
43043
42842
|
}
|
|
43044
42843
|
async function searchGammaMarkets(query, closed) {
|
|
43045
|
-
const url =
|
|
42844
|
+
const url = buildUrl2(GAMMA_BASE_URL, "/public-search", {
|
|
43046
42845
|
q: query,
|
|
43047
42846
|
limit_per_type: SEARCH_EVENT_LIMIT,
|
|
43048
42847
|
events_status: closed ? "closed" : "active"
|
|
@@ -43080,7 +42879,7 @@ async function resolveMarket(selector) {
|
|
|
43080
42879
|
if (gamma.rows.length > 0) {
|
|
43081
42880
|
return { url: gamma.url, market: normalizeGammaMarket(gamma.rows[0]) };
|
|
43082
42881
|
}
|
|
43083
|
-
const clobUrl =
|
|
42882
|
+
const clobUrl = buildUrl2(
|
|
43084
42883
|
CLOB_BASE_URL,
|
|
43085
42884
|
`/clob-markets/${selector.conditionId}`
|
|
43086
42885
|
);
|
|
@@ -43106,7 +42905,7 @@ async function resolveMarket(selector) {
|
|
|
43106
42905
|
}
|
|
43107
42906
|
}
|
|
43108
42907
|
async function fetchOrderbook(tokenId) {
|
|
43109
|
-
const url =
|
|
42908
|
+
const url = buildUrl2(CLOB_BASE_URL, "/book", { token_id: tokenId });
|
|
43110
42909
|
try {
|
|
43111
42910
|
const data = await fetchPolymarketJson(url, "CLOB /book");
|
|
43112
42911
|
const raw = asRecord(data);
|
|
@@ -43132,11 +42931,11 @@ async function fetchPriceHistory(tokenId, interval, fidelity) {
|
|
|
43132
42931
|
interval,
|
|
43133
42932
|
bucket_seconds: fidelity * 60
|
|
43134
42933
|
};
|
|
43135
|
-
const url =
|
|
42934
|
+
const url = buildUrl2(DATA_API_BASE_URL, "/v2/prices-history", params);
|
|
43136
42935
|
const rows = [];
|
|
43137
42936
|
let cursor = null;
|
|
43138
42937
|
for (let page = 0; page < MAX_HISTORY_PAGES; page++) {
|
|
43139
|
-
const pageUrl = cursor ?
|
|
42938
|
+
const pageUrl = cursor ? buildUrl2(DATA_API_BASE_URL, "/v2/prices-history", {
|
|
43140
42939
|
...params,
|
|
43141
42940
|
cursor
|
|
43142
42941
|
}) : url;
|
|
@@ -43154,7 +42953,7 @@ async function fetchPriceHistory(tokenId, interval, fidelity) {
|
|
|
43154
42953
|
}
|
|
43155
42954
|
async function fetchPositions(user, limit, cursor) {
|
|
43156
42955
|
const label = "Data API /v2/positions";
|
|
43157
|
-
const url =
|
|
42956
|
+
const url = buildUrl2(DATA_API_BASE_URL, "/v2/positions", {
|
|
43158
42957
|
user,
|
|
43159
42958
|
limit,
|
|
43160
42959
|
cursor: cursor ?? void 0
|
|
@@ -43168,7 +42967,7 @@ async function fetchPositions(user, limit, cursor) {
|
|
|
43168
42967
|
}
|
|
43169
42968
|
async function fetchActivity(user, limit, cursor) {
|
|
43170
42969
|
const label = "Data API /v2/activity";
|
|
43171
|
-
const url =
|
|
42970
|
+
const url = buildUrl2(DATA_API_BASE_URL, "/v2/activity", {
|
|
43172
42971
|
user,
|
|
43173
42972
|
limit,
|
|
43174
42973
|
cursor: cursor ?? void 0
|
|
@@ -43828,6 +43627,1376 @@ Don't use when:
|
|
|
43828
43627
|
];
|
|
43829
43628
|
}
|
|
43830
43629
|
|
|
43630
|
+
// src/tools/kalshi/client.ts
|
|
43631
|
+
var KALSHI_BASE_URL = "https://api.elections.kalshi.com/trade-api/v2";
|
|
43632
|
+
var KALSHI_USER_AGENT = "HiveIntelligence/1.0 (+https://hiveintelligence.xyz)";
|
|
43633
|
+
var KALSHI_TIMEOUT_MS = 1e4;
|
|
43634
|
+
var KALSHI_NETWORK_RETRIES = 1;
|
|
43635
|
+
var NETWORK_RETRY_DELAY_MS = 150;
|
|
43636
|
+
var UPSTREAM_ERROR_MAX_CHARS2 = 200;
|
|
43637
|
+
var KalshiInputError = class extends Error {
|
|
43638
|
+
};
|
|
43639
|
+
var KalshiNotFoundError = class extends Error {
|
|
43640
|
+
constructor(message, url) {
|
|
43641
|
+
super(message);
|
|
43642
|
+
this.url = url;
|
|
43643
|
+
}
|
|
43644
|
+
url;
|
|
43645
|
+
};
|
|
43646
|
+
function buildUrl3(path, params = {}, base = KALSHI_BASE_URL) {
|
|
43647
|
+
const url = new URL(`${base}${path}`);
|
|
43648
|
+
for (const [key, value] of Object.entries(params)) {
|
|
43649
|
+
if (value === void 0) continue;
|
|
43650
|
+
url.searchParams.set(key, String(value));
|
|
43651
|
+
}
|
|
43652
|
+
return url.toString();
|
|
43653
|
+
}
|
|
43654
|
+
function clip(text) {
|
|
43655
|
+
return text.trim().slice(0, UPSTREAM_ERROR_MAX_CHARS2);
|
|
43656
|
+
}
|
|
43657
|
+
function upstreamErrorText2(bodyText) {
|
|
43658
|
+
try {
|
|
43659
|
+
const parsed = JSON.parse(bodyText);
|
|
43660
|
+
if (parsed && typeof parsed === "object") {
|
|
43661
|
+
const err = parsed.error;
|
|
43662
|
+
if (typeof err === "string" && err.trim()) return clip(err);
|
|
43663
|
+
if (err && typeof err === "object") {
|
|
43664
|
+
const record = err;
|
|
43665
|
+
const parts = [record.code, record.message, record.details].filter(
|
|
43666
|
+
(part) => typeof part === "string" && part.trim() !== ""
|
|
43667
|
+
);
|
|
43668
|
+
if (parts.length > 0) return clip(parts.join(": "));
|
|
43669
|
+
}
|
|
43670
|
+
if (typeof parsed.msg === "string" && parsed.msg.trim()) {
|
|
43671
|
+
return clip(parsed.msg);
|
|
43672
|
+
}
|
|
43673
|
+
if (typeof parsed.message === "string" && parsed.message.trim()) {
|
|
43674
|
+
return clip(parsed.message);
|
|
43675
|
+
}
|
|
43676
|
+
}
|
|
43677
|
+
} catch {
|
|
43678
|
+
}
|
|
43679
|
+
const trimmed = bodyText.trim();
|
|
43680
|
+
return trimmed ? clip(trimmed) : "";
|
|
43681
|
+
}
|
|
43682
|
+
function describeNetworkError(error) {
|
|
43683
|
+
if (!(error instanceof Error)) return String(error);
|
|
43684
|
+
const cause = error.cause;
|
|
43685
|
+
const code = cause && typeof cause === "object" && typeof cause.code === "string" ? cause.code : null;
|
|
43686
|
+
return code ? `${error.message} (${code})` : error.message;
|
|
43687
|
+
}
|
|
43688
|
+
function sleep2(ms) {
|
|
43689
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
43690
|
+
}
|
|
43691
|
+
async function fetchKalshiJson(url, label, timeoutMs = KALSHI_TIMEOUT_MS) {
|
|
43692
|
+
let response = null;
|
|
43693
|
+
for (let attempt = 0; attempt <= KALSHI_NETWORK_RETRIES; attempt++) {
|
|
43694
|
+
const controller = new AbortController();
|
|
43695
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
43696
|
+
try {
|
|
43697
|
+
response = await fetch(url, {
|
|
43698
|
+
method: "GET",
|
|
43699
|
+
headers: {
|
|
43700
|
+
Accept: "application/json",
|
|
43701
|
+
"User-Agent": KALSHI_USER_AGENT
|
|
43702
|
+
},
|
|
43703
|
+
signal: controller.signal
|
|
43704
|
+
});
|
|
43705
|
+
break;
|
|
43706
|
+
} catch (error) {
|
|
43707
|
+
if (controller.signal.aborted) {
|
|
43708
|
+
throw new Error(
|
|
43709
|
+
`Kalshi ${label} timed out after ${timeoutMs} ms. Retry once; if it persists the upstream is degraded.`
|
|
43710
|
+
);
|
|
43711
|
+
}
|
|
43712
|
+
if (attempt < KALSHI_NETWORK_RETRIES) {
|
|
43713
|
+
await sleep2(NETWORK_RETRY_DELAY_MS);
|
|
43714
|
+
continue;
|
|
43715
|
+
}
|
|
43716
|
+
throw new Error(
|
|
43717
|
+
`Kalshi ${label} request failed after ${attempt + 1} attempts (${describeNetworkError(error)}); the upstream is temporarily unreachable. Retry shortly.`
|
|
43718
|
+
);
|
|
43719
|
+
} finally {
|
|
43720
|
+
clearTimeout(timer);
|
|
43721
|
+
}
|
|
43722
|
+
}
|
|
43723
|
+
if (!response) {
|
|
43724
|
+
throw new Error(`Kalshi ${label} produced no response.`);
|
|
43725
|
+
}
|
|
43726
|
+
const bodyText = await response.text();
|
|
43727
|
+
if (!response.ok) {
|
|
43728
|
+
const detail = upstreamErrorText2(bodyText);
|
|
43729
|
+
const suffix = detail ? `: ${detail}` : "";
|
|
43730
|
+
const statusText = response.statusText ? ` ${response.statusText}` : "";
|
|
43731
|
+
if (response.status === 404) {
|
|
43732
|
+
throw new KalshiNotFoundError(
|
|
43733
|
+
`Kalshi ${label} responded 404${suffix}`,
|
|
43734
|
+
url
|
|
43735
|
+
);
|
|
43736
|
+
}
|
|
43737
|
+
if (response.status === 400 || response.status === 422) {
|
|
43738
|
+
throw new KalshiInputError(
|
|
43739
|
+
`Invalid arguments: Kalshi ${label} rejected the request (${response.status})${suffix}.`
|
|
43740
|
+
);
|
|
43741
|
+
}
|
|
43742
|
+
if (response.status === 429) {
|
|
43743
|
+
throw new Error(
|
|
43744
|
+
`Kalshi ${label} responded 429 Too Many Requests${suffix}. Kalshi rate-limited this client; back off and retry.`
|
|
43745
|
+
);
|
|
43746
|
+
}
|
|
43747
|
+
throw new Error(
|
|
43748
|
+
`Kalshi ${label} responded ${response.status}${statusText}${suffix}`
|
|
43749
|
+
);
|
|
43750
|
+
}
|
|
43751
|
+
try {
|
|
43752
|
+
return JSON.parse(bodyText);
|
|
43753
|
+
} catch {
|
|
43754
|
+
throw new Error(`Kalshi ${label} returned a non-JSON body.`);
|
|
43755
|
+
}
|
|
43756
|
+
}
|
|
43757
|
+
|
|
43758
|
+
// src/tools/kalshi/api.ts
|
|
43759
|
+
var DEFAULT_LIMIT4 = 10;
|
|
43760
|
+
var MAX_LIMIT4 = 50;
|
|
43761
|
+
var DEFAULT_TRADES_LIMIT = 50;
|
|
43762
|
+
var MAX_TRADES_LIMIT = 200;
|
|
43763
|
+
var DEFAULT_BOOK_DEPTH2 = 10;
|
|
43764
|
+
var MAX_BOOK_DEPTH2 = 100;
|
|
43765
|
+
var SEARCH_PAGE_SIZE = 200;
|
|
43766
|
+
var SEARCH_MAX_PAGES = 3;
|
|
43767
|
+
var EVENT_MARKETS_PREVIEW2 = 25;
|
|
43768
|
+
var MAX_CANDLES = 500;
|
|
43769
|
+
var PERIOD_INTERVALS = [1, 60, 1440];
|
|
43770
|
+
var DEFAULT_PERIOD_INTERVAL = 60;
|
|
43771
|
+
var DEFAULT_WINDOW_CANDLES = {
|
|
43772
|
+
1: 480,
|
|
43773
|
+
60: 480,
|
|
43774
|
+
1440: 365
|
|
43775
|
+
};
|
|
43776
|
+
var MARKET_STATUS_FILTERS = [
|
|
43777
|
+
"unopened",
|
|
43778
|
+
"open",
|
|
43779
|
+
"paused",
|
|
43780
|
+
"closed",
|
|
43781
|
+
"settled"
|
|
43782
|
+
];
|
|
43783
|
+
var EVENT_STATUS_FILTERS = [
|
|
43784
|
+
"unopened",
|
|
43785
|
+
"open",
|
|
43786
|
+
"closed",
|
|
43787
|
+
"settled"
|
|
43788
|
+
];
|
|
43789
|
+
var ANY_STATUS = "all";
|
|
43790
|
+
var TICKER_RE = /^[A-Z0-9][A-Z0-9._-]{0,119}$/;
|
|
43791
|
+
var QUERY_MAX_CHARS2 = 200;
|
|
43792
|
+
var CURSOR_MAX_CHARS = 2e3;
|
|
43793
|
+
var RULES_MAX_CHARS = 1200;
|
|
43794
|
+
var MS_THRESHOLD = 1e11;
|
|
43795
|
+
function invalid2(message) {
|
|
43796
|
+
throw new KalshiInputError(`Invalid arguments: ${message}`);
|
|
43797
|
+
}
|
|
43798
|
+
function parseFiniteNumber3(value) {
|
|
43799
|
+
const num = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
43800
|
+
return Number.isFinite(num) ? num : null;
|
|
43801
|
+
}
|
|
43802
|
+
function asRecord2(value) {
|
|
43803
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
43804
|
+
}
|
|
43805
|
+
function asRecordArray2(value, label) {
|
|
43806
|
+
if (!Array.isArray(value)) {
|
|
43807
|
+
throw new Error(
|
|
43808
|
+
`Kalshi ${label} returned an unexpected payload shape (expected a JSON array).`
|
|
43809
|
+
);
|
|
43810
|
+
}
|
|
43811
|
+
return value.filter((item) => asRecord2(item) !== null);
|
|
43812
|
+
}
|
|
43813
|
+
function optionalString2(value) {
|
|
43814
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
43815
|
+
}
|
|
43816
|
+
function optionalBoolean2(value) {
|
|
43817
|
+
return typeof value === "boolean" ? value : null;
|
|
43818
|
+
}
|
|
43819
|
+
function round4(value) {
|
|
43820
|
+
return Number(value.toFixed(4));
|
|
43821
|
+
}
|
|
43822
|
+
function dollarsToCents(dollars) {
|
|
43823
|
+
return Math.round(dollars * 1e4) / 100;
|
|
43824
|
+
}
|
|
43825
|
+
function readPrice(raw, base) {
|
|
43826
|
+
const dollars = parseFiniteNumber3(raw[`${base}_dollars`]);
|
|
43827
|
+
if (dollars !== null) return { dollars, cents: dollarsToCents(dollars) };
|
|
43828
|
+
const legacyCents = parseFiniteNumber3(raw[base]);
|
|
43829
|
+
if (legacyCents !== null) {
|
|
43830
|
+
return { dollars: round4(legacyCents / 100), cents: legacyCents };
|
|
43831
|
+
}
|
|
43832
|
+
return { dollars: null, cents: null };
|
|
43833
|
+
}
|
|
43834
|
+
function readCount(raw, base) {
|
|
43835
|
+
const fp = parseFiniteNumber3(raw[`${base}_fp`]);
|
|
43836
|
+
return fp !== null ? fp : parseFiniteNumber3(raw[base]);
|
|
43837
|
+
}
|
|
43838
|
+
function unixSecondsToIso2(seconds) {
|
|
43839
|
+
return new Date(seconds * 1e3).toISOString();
|
|
43840
|
+
}
|
|
43841
|
+
function boundedText(value, max) {
|
|
43842
|
+
const text = optionalString2(value);
|
|
43843
|
+
if (!text) return null;
|
|
43844
|
+
return text.length > max ? `${text.slice(0, max)}...` : text;
|
|
43845
|
+
}
|
|
43846
|
+
function parseBoundedInteger2(value, field, min, max, fallback) {
|
|
43847
|
+
if (value === void 0 || value === null) return fallback;
|
|
43848
|
+
const num = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
43849
|
+
if (!Number.isInteger(num) || num < min || num > max) {
|
|
43850
|
+
invalid2(
|
|
43851
|
+
`${field} must be an integer between ${min} and ${max}; got ${JSON.stringify(value)}.`
|
|
43852
|
+
);
|
|
43853
|
+
}
|
|
43854
|
+
return num;
|
|
43855
|
+
}
|
|
43856
|
+
function parseLimit2(value) {
|
|
43857
|
+
return parseBoundedInteger2(value, "limit", 1, MAX_LIMIT4, DEFAULT_LIMIT4);
|
|
43858
|
+
}
|
|
43859
|
+
function parseTradesLimit(value) {
|
|
43860
|
+
return parseBoundedInteger2(
|
|
43861
|
+
value,
|
|
43862
|
+
"limit",
|
|
43863
|
+
1,
|
|
43864
|
+
MAX_TRADES_LIMIT,
|
|
43865
|
+
DEFAULT_TRADES_LIMIT
|
|
43866
|
+
);
|
|
43867
|
+
}
|
|
43868
|
+
function parseBookDepth2(value) {
|
|
43869
|
+
return parseBoundedInteger2(
|
|
43870
|
+
value,
|
|
43871
|
+
"depth",
|
|
43872
|
+
1,
|
|
43873
|
+
MAX_BOOK_DEPTH2,
|
|
43874
|
+
DEFAULT_BOOK_DEPTH2
|
|
43875
|
+
);
|
|
43876
|
+
}
|
|
43877
|
+
function parseOptionalBoolean2(value, field, fallback) {
|
|
43878
|
+
if (value === void 0 || value === null) return fallback;
|
|
43879
|
+
if (typeof value === "boolean") return value;
|
|
43880
|
+
if (value === "true") return true;
|
|
43881
|
+
if (value === "false") return false;
|
|
43882
|
+
return invalid2(`${field} must be a boolean; got ${JSON.stringify(value)}.`);
|
|
43883
|
+
}
|
|
43884
|
+
function parseOptionalQuery2(value) {
|
|
43885
|
+
if (value === void 0 || value === null) return null;
|
|
43886
|
+
if (typeof value !== "string") {
|
|
43887
|
+
invalid2(`query must be a string; got ${JSON.stringify(value)}.`);
|
|
43888
|
+
}
|
|
43889
|
+
const query = value.trim();
|
|
43890
|
+
if (!query) return null;
|
|
43891
|
+
if (query.length > QUERY_MAX_CHARS2) {
|
|
43892
|
+
invalid2(`query must be at most ${QUERY_MAX_CHARS2} characters.`);
|
|
43893
|
+
}
|
|
43894
|
+
return query;
|
|
43895
|
+
}
|
|
43896
|
+
function parseOptionalCursor(value) {
|
|
43897
|
+
if (value === void 0 || value === null) return null;
|
|
43898
|
+
const cursor = typeof value === "string" ? value.trim() : "";
|
|
43899
|
+
if (!cursor) return null;
|
|
43900
|
+
if (cursor.length > CURSOR_MAX_CHARS) {
|
|
43901
|
+
invalid2(
|
|
43902
|
+
`cursor must be the opaque next_cursor string from a previous response (at most ${CURSOR_MAX_CHARS} characters).`
|
|
43903
|
+
);
|
|
43904
|
+
}
|
|
43905
|
+
return cursor;
|
|
43906
|
+
}
|
|
43907
|
+
function parseOptionalTicker(value, field) {
|
|
43908
|
+
if (value === void 0 || value === null) return null;
|
|
43909
|
+
const ticker = typeof value === "string" ? value.trim().toUpperCase() : "";
|
|
43910
|
+
if (ticker === "") return null;
|
|
43911
|
+
if (!TICKER_RE.test(ticker)) {
|
|
43912
|
+
invalid2(
|
|
43913
|
+
`${field} must be a Kalshi ticker (letters, digits, "-", ".", "_"), e.g. "KXBTCD-26SEP1612-T85799.99"; got ${JSON.stringify(value)}.`
|
|
43914
|
+
);
|
|
43915
|
+
}
|
|
43916
|
+
return ticker;
|
|
43917
|
+
}
|
|
43918
|
+
function parseRequiredTicker(value, field = "ticker") {
|
|
43919
|
+
const ticker = parseOptionalTicker(value, field);
|
|
43920
|
+
if (!ticker) {
|
|
43921
|
+
invalid2(
|
|
43922
|
+
`${field} is required. Pass a Kalshi market ticker such as "KXNIREFCALL-30JAN01-YES" (find one with kalshi_search_markets or kalshi_get_events).`
|
|
43923
|
+
);
|
|
43924
|
+
}
|
|
43925
|
+
return ticker;
|
|
43926
|
+
}
|
|
43927
|
+
function parseStatusFilter(value, allowed, fallback) {
|
|
43928
|
+
if (value === void 0 || value === null) return fallback;
|
|
43929
|
+
const status = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
43930
|
+
if (status === ANY_STATUS) return null;
|
|
43931
|
+
if (!allowed.includes(status)) {
|
|
43932
|
+
invalid2(
|
|
43933
|
+
`status must be one of ${[...allowed, ANY_STATUS].map((item) => `"${item}"`).join(", ")}; got ${JSON.stringify(value)}.`
|
|
43934
|
+
);
|
|
43935
|
+
}
|
|
43936
|
+
return status;
|
|
43937
|
+
}
|
|
43938
|
+
function parsePeriodInterval(value) {
|
|
43939
|
+
if (value === void 0 || value === null) return DEFAULT_PERIOD_INTERVAL;
|
|
43940
|
+
const num = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
43941
|
+
if (!PERIOD_INTERVALS.includes(num)) {
|
|
43942
|
+
invalid2(
|
|
43943
|
+
`period_interval must be one of ${PERIOD_INTERVALS.join(", ")} (minutes per candle); got ${JSON.stringify(value)}.`
|
|
43944
|
+
);
|
|
43945
|
+
}
|
|
43946
|
+
return num;
|
|
43947
|
+
}
|
|
43948
|
+
function parseTimestamp(value, field) {
|
|
43949
|
+
if (value === void 0 || value === null) return null;
|
|
43950
|
+
if (typeof value === "string" && value.trim() === "") return null;
|
|
43951
|
+
const numeric = parseFiniteNumber3(value);
|
|
43952
|
+
if (numeric !== null) {
|
|
43953
|
+
if (numeric <= 0) {
|
|
43954
|
+
invalid2(`${field} must be a positive unix timestamp or an ISO 8601 datetime; got ${JSON.stringify(value)}.`);
|
|
43955
|
+
}
|
|
43956
|
+
return Math.floor(numeric >= MS_THRESHOLD ? numeric / 1e3 : numeric);
|
|
43957
|
+
}
|
|
43958
|
+
if (typeof value === "string") {
|
|
43959
|
+
const ms = Date.parse(value);
|
|
43960
|
+
if (Number.isFinite(ms)) return Math.floor(ms / 1e3);
|
|
43961
|
+
}
|
|
43962
|
+
return invalid2(
|
|
43963
|
+
`${field} must be an ISO 8601 datetime (e.g. "2026-09-01T00:00:00Z") or a unix timestamp in seconds; got ${JSON.stringify(value)}.`
|
|
43964
|
+
);
|
|
43965
|
+
}
|
|
43966
|
+
function resolveCandleWindow(args, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
43967
|
+
const interval = parsePeriodInterval(args?.period_interval);
|
|
43968
|
+
const intervalSeconds = interval * 60;
|
|
43969
|
+
const end = parseTimestamp(args?.end, "end") ?? nowSeconds;
|
|
43970
|
+
const requestedStart = parseTimestamp(args?.start, "start");
|
|
43971
|
+
let start = requestedStart ?? end - DEFAULT_WINDOW_CANDLES[interval] * intervalSeconds;
|
|
43972
|
+
if (start >= end) {
|
|
43973
|
+
invalid2(
|
|
43974
|
+
`start (${unixSecondsToIso2(start)}) must be before end (${unixSecondsToIso2(end)}).`
|
|
43975
|
+
);
|
|
43976
|
+
}
|
|
43977
|
+
const maxSpan = MAX_CANDLES * intervalSeconds;
|
|
43978
|
+
let truncated = false;
|
|
43979
|
+
if (end - start > maxSpan) {
|
|
43980
|
+
start = end - maxSpan;
|
|
43981
|
+
truncated = true;
|
|
43982
|
+
}
|
|
43983
|
+
return {
|
|
43984
|
+
start_ts: start,
|
|
43985
|
+
end_ts: end,
|
|
43986
|
+
period_interval: interval,
|
|
43987
|
+
requested_start_ts: requestedStart,
|
|
43988
|
+
window_truncated: truncated,
|
|
43989
|
+
expected_candles: Math.ceil((end - start) / intervalSeconds)
|
|
43990
|
+
};
|
|
43991
|
+
}
|
|
43992
|
+
function matchesQuery2(fields, query) {
|
|
43993
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
43994
|
+
if (terms.length === 0) return true;
|
|
43995
|
+
const text = fields.filter((value) => typeof value === "string").join(" ").toLowerCase();
|
|
43996
|
+
return terms.every((term) => text.includes(term));
|
|
43997
|
+
}
|
|
43998
|
+
function seriesTickerFromTicker(ticker) {
|
|
43999
|
+
const [head] = ticker.split("-");
|
|
44000
|
+
return head || ticker;
|
|
44001
|
+
}
|
|
44002
|
+
function toMarketRow3(raw) {
|
|
44003
|
+
const yesBid = readPrice(raw, "yes_bid");
|
|
44004
|
+
const yesAsk = readPrice(raw, "yes_ask");
|
|
44005
|
+
const last = readPrice(raw, "last_price");
|
|
44006
|
+
return {
|
|
44007
|
+
ticker: optionalString2(raw.ticker) ?? "",
|
|
44008
|
+
event_ticker: optionalString2(raw.event_ticker),
|
|
44009
|
+
title: optionalString2(raw.title),
|
|
44010
|
+
yes_sub_title: optionalString2(raw.yes_sub_title),
|
|
44011
|
+
no_sub_title: optionalString2(raw.no_sub_title),
|
|
44012
|
+
status: optionalString2(raw.status),
|
|
44013
|
+
market_type: optionalString2(raw.market_type),
|
|
44014
|
+
yes_bid: yesBid.dollars,
|
|
44015
|
+
yes_bid_cents: yesBid.cents,
|
|
44016
|
+
yes_ask: yesAsk.dollars,
|
|
44017
|
+
yes_ask_cents: yesAsk.cents,
|
|
44018
|
+
last_price: last.dollars,
|
|
44019
|
+
last_price_cents: last.cents,
|
|
44020
|
+
volume: readCount(raw, "volume"),
|
|
44021
|
+
volume_24h: readCount(raw, "volume_24h"),
|
|
44022
|
+
open_interest: readCount(raw, "open_interest"),
|
|
44023
|
+
open_time: optionalString2(raw.open_time),
|
|
44024
|
+
close_time: optionalString2(raw.close_time)
|
|
44025
|
+
};
|
|
44026
|
+
}
|
|
44027
|
+
function normalizeMarket(raw) {
|
|
44028
|
+
const row = toMarketRow3(raw);
|
|
44029
|
+
const noBid = readPrice(raw, "no_bid");
|
|
44030
|
+
const noAsk = readPrice(raw, "no_ask");
|
|
44031
|
+
const previous = readPrice(raw, "previous_price");
|
|
44032
|
+
const midpoint = row.yes_bid !== null && row.yes_ask !== null ? round4((row.yes_bid + row.yes_ask) / 2) : null;
|
|
44033
|
+
const spreadCents = row.yes_bid_cents !== null && row.yes_ask_cents !== null ? Number((row.yes_ask_cents - row.yes_bid_cents).toFixed(2)) : null;
|
|
44034
|
+
return {
|
|
44035
|
+
...row,
|
|
44036
|
+
series_ticker: seriesTickerFromTicker(row.ticker),
|
|
44037
|
+
subtitle: optionalString2(raw.subtitle),
|
|
44038
|
+
result: optionalString2(raw.result),
|
|
44039
|
+
no_bid: noBid.dollars,
|
|
44040
|
+
no_bid_cents: noBid.cents,
|
|
44041
|
+
no_ask: noAsk.dollars,
|
|
44042
|
+
no_ask_cents: noAsk.cents,
|
|
44043
|
+
yes_midpoint: midpoint,
|
|
44044
|
+
yes_spread_cents: spreadCents,
|
|
44045
|
+
previous_price: previous.dollars,
|
|
44046
|
+
previous_price_cents: previous.cents,
|
|
44047
|
+
previous_yes_bid: readPrice(raw, "previous_yes_bid").dollars,
|
|
44048
|
+
previous_yes_ask: readPrice(raw, "previous_yes_ask").dollars,
|
|
44049
|
+
yes_bid_size: readCount(raw, "yes_bid_size"),
|
|
44050
|
+
yes_ask_size: readCount(raw, "yes_ask_size"),
|
|
44051
|
+
liquidity_dollars: parseFiniteNumber3(raw.liquidity_dollars),
|
|
44052
|
+
notional_value_dollars: parseFiniteNumber3(raw.notional_value_dollars),
|
|
44053
|
+
expiration_time: optionalString2(raw.expiration_time),
|
|
44054
|
+
expected_expiration_time: optionalString2(raw.expected_expiration_time),
|
|
44055
|
+
latest_expiration_time: optionalString2(raw.latest_expiration_time),
|
|
44056
|
+
can_close_early: optionalBoolean2(raw.can_close_early),
|
|
44057
|
+
early_close_condition: optionalString2(raw.early_close_condition),
|
|
44058
|
+
settlement_timer_seconds: parseFiniteNumber3(raw.settlement_timer_seconds),
|
|
44059
|
+
strike_type: optionalString2(raw.strike_type),
|
|
44060
|
+
floor_strike: parseFiniteNumber3(raw.floor_strike),
|
|
44061
|
+
cap_strike: parseFiniteNumber3(raw.cap_strike),
|
|
44062
|
+
custom_strike: asRecord2(raw.custom_strike),
|
|
44063
|
+
is_provisional: optionalBoolean2(raw.is_provisional),
|
|
44064
|
+
mve_collection_ticker: optionalString2(raw.mve_collection_ticker),
|
|
44065
|
+
mve_selected_legs: Array.isArray(raw.mve_selected_legs) ? raw.mve_selected_legs : null,
|
|
44066
|
+
price_level_structure: optionalString2(raw.price_level_structure),
|
|
44067
|
+
price_ranges: Array.isArray(raw.price_ranges) ? raw.price_ranges : null,
|
|
44068
|
+
rules_primary: boundedText(raw.rules_primary, RULES_MAX_CHARS),
|
|
44069
|
+
rules_secondary: boundedText(raw.rules_secondary, RULES_MAX_CHARS),
|
|
44070
|
+
created_time: optionalString2(raw.created_time),
|
|
44071
|
+
updated_time: optionalString2(raw.updated_time)
|
|
44072
|
+
};
|
|
44073
|
+
}
|
|
44074
|
+
function toEventRow2(raw, withNested, extraMarkets = void 0) {
|
|
44075
|
+
const rawMarkets = Array.isArray(raw.markets) ? raw.markets : Array.isArray(extraMarkets) ? extraMarkets : [];
|
|
44076
|
+
const markets = rawMarkets.map(asRecord2).filter((item) => item !== null).map(toMarketRow3);
|
|
44077
|
+
const sources = Array.isArray(raw.settlement_sources) ? raw.settlement_sources.map((source) => {
|
|
44078
|
+
const record = asRecord2(source);
|
|
44079
|
+
return record ? optionalString2(record.name) : optionalString2(source);
|
|
44080
|
+
}).filter((name) => name !== null) : [];
|
|
44081
|
+
return {
|
|
44082
|
+
event_ticker: optionalString2(raw.event_ticker) ?? "",
|
|
44083
|
+
series_ticker: optionalString2(raw.series_ticker),
|
|
44084
|
+
title: optionalString2(raw.title),
|
|
44085
|
+
sub_title: optionalString2(raw.sub_title),
|
|
44086
|
+
category: optionalString2(raw.category),
|
|
44087
|
+
mutually_exclusive: optionalBoolean2(raw.mutually_exclusive),
|
|
44088
|
+
strike_date: optionalString2(raw.strike_date),
|
|
44089
|
+
strike_period: optionalString2(raw.strike_period),
|
|
44090
|
+
collateral_return_type: optionalString2(raw.collateral_return_type),
|
|
44091
|
+
settlement_sources: sources,
|
|
44092
|
+
last_updated_ts: optionalString2(raw.last_updated_ts),
|
|
44093
|
+
market_count: withNested ? markets.length : null,
|
|
44094
|
+
markets: withNested ? markets.slice(0, EVENT_MARKETS_PREVIEW2) : null,
|
|
44095
|
+
markets_truncated: withNested && markets.length > EVENT_MARKETS_PREVIEW2
|
|
44096
|
+
};
|
|
44097
|
+
}
|
|
44098
|
+
function parseLevels2(value, unit) {
|
|
44099
|
+
if (!Array.isArray(value)) return [];
|
|
44100
|
+
const levels = [];
|
|
44101
|
+
for (const item of value) {
|
|
44102
|
+
if (!Array.isArray(item) || item.length < 2) continue;
|
|
44103
|
+
const rawPrice = parseFiniteNumber3(item[0]);
|
|
44104
|
+
const size = parseFiniteNumber3(item[1]);
|
|
44105
|
+
if (rawPrice === null || size === null) continue;
|
|
44106
|
+
if (unit === "dollars") {
|
|
44107
|
+
levels.push({ price: rawPrice, cents: dollarsToCents(rawPrice), size });
|
|
44108
|
+
} else {
|
|
44109
|
+
levels.push({ price: round4(rawPrice / 100), cents: rawPrice, size });
|
|
44110
|
+
}
|
|
44111
|
+
}
|
|
44112
|
+
return levels.sort((a, b) => b.price - a.price);
|
|
44113
|
+
}
|
|
44114
|
+
function summarizeOrderbook2(raw, ticker, depth) {
|
|
44115
|
+
const fp = asRecord2(raw.orderbook_fp);
|
|
44116
|
+
const legacy = asRecord2(raw.orderbook);
|
|
44117
|
+
const yes = fp ? parseLevels2(fp.yes_dollars, "dollars") : parseLevels2(legacy?.yes, "cents");
|
|
44118
|
+
const no = fp ? parseLevels2(fp.no_dollars, "dollars") : parseLevels2(legacy?.no, "cents");
|
|
44119
|
+
const bestYesBid = yes[0]?.price ?? null;
|
|
44120
|
+
const bestNoBid = no[0]?.price ?? null;
|
|
44121
|
+
const bestYesAsk = bestNoBid !== null ? round4(1 - bestNoBid) : null;
|
|
44122
|
+
const bestNoAsk = bestYesBid !== null ? round4(1 - bestYesBid) : null;
|
|
44123
|
+
const spread = bestYesBid !== null && bestYesAsk !== null ? round4(bestYesAsk - bestYesBid) : null;
|
|
44124
|
+
const midpoint = bestYesBid !== null && bestYesAsk !== null ? round4((bestYesAsk + bestYesBid) / 2) : null;
|
|
44125
|
+
return {
|
|
44126
|
+
ticker,
|
|
44127
|
+
yes_bids: yes.slice(0, depth),
|
|
44128
|
+
no_bids: no.slice(0, depth),
|
|
44129
|
+
yes_levels_total: yes.length,
|
|
44130
|
+
no_levels_total: no.length,
|
|
44131
|
+
best_yes_bid: bestYesBid,
|
|
44132
|
+
best_yes_bid_cents: bestYesBid !== null ? dollarsToCents(bestYesBid) : null,
|
|
44133
|
+
best_yes_ask: bestYesAsk,
|
|
44134
|
+
best_yes_ask_cents: bestYesAsk !== null ? dollarsToCents(bestYesAsk) : null,
|
|
44135
|
+
best_no_bid: bestNoBid,
|
|
44136
|
+
best_no_ask: bestNoAsk,
|
|
44137
|
+
yes_spread: spread,
|
|
44138
|
+
yes_midpoint: midpoint
|
|
44139
|
+
};
|
|
44140
|
+
}
|
|
44141
|
+
function toTradeRow(raw) {
|
|
44142
|
+
const yesPrice2 = readPrice(raw, "yes_price");
|
|
44143
|
+
const noPrice = readPrice(raw, "no_price");
|
|
44144
|
+
return {
|
|
44145
|
+
trade_id: optionalString2(raw.trade_id),
|
|
44146
|
+
ticker: optionalString2(raw.ticker),
|
|
44147
|
+
created_time: optionalString2(raw.created_time),
|
|
44148
|
+
count: readCount(raw, "count"),
|
|
44149
|
+
yes_price: yesPrice2.dollars,
|
|
44150
|
+
yes_price_cents: yesPrice2.cents,
|
|
44151
|
+
no_price: noPrice.dollars,
|
|
44152
|
+
no_price_cents: noPrice.cents,
|
|
44153
|
+
taker_side: optionalString2(raw.taker_side),
|
|
44154
|
+
taker_book_side: optionalString2(raw.taker_book_side),
|
|
44155
|
+
is_block_trade: optionalBoolean2(raw.is_block_trade)
|
|
44156
|
+
};
|
|
44157
|
+
}
|
|
44158
|
+
function toCandle(raw) {
|
|
44159
|
+
const endTs = parseFiniteNumber3(raw.end_period_ts);
|
|
44160
|
+
if (endTs === null) return null;
|
|
44161
|
+
const price = asRecord2(raw.price) ?? {};
|
|
44162
|
+
const yesBid = asRecord2(raw.yes_bid) ?? {};
|
|
44163
|
+
const yesAsk = asRecord2(raw.yes_ask) ?? {};
|
|
44164
|
+
return {
|
|
44165
|
+
end_period_ts: endTs,
|
|
44166
|
+
end_time: unixSecondsToIso2(endTs),
|
|
44167
|
+
open: readPrice(price, "open").dollars,
|
|
44168
|
+
high: readPrice(price, "high").dollars,
|
|
44169
|
+
low: readPrice(price, "low").dollars,
|
|
44170
|
+
close: readPrice(price, "close").dollars,
|
|
44171
|
+
mean: readPrice(price, "mean").dollars,
|
|
44172
|
+
previous: readPrice(price, "previous").dollars,
|
|
44173
|
+
yes_bid_close: readPrice(yesBid, "close").dollars,
|
|
44174
|
+
yes_ask_close: readPrice(yesAsk, "close").dollars,
|
|
44175
|
+
volume: readCount(raw, "volume"),
|
|
44176
|
+
open_interest: readCount(raw, "open_interest")
|
|
44177
|
+
};
|
|
44178
|
+
}
|
|
44179
|
+
function summarizeCandles(candles) {
|
|
44180
|
+
const traded = candles.filter(
|
|
44181
|
+
(candle) => candle.close !== null
|
|
44182
|
+
);
|
|
44183
|
+
if (traded.length === 0) return null;
|
|
44184
|
+
const first = traded[0];
|
|
44185
|
+
const last = traded[traded.length - 1];
|
|
44186
|
+
let minLow = first.close;
|
|
44187
|
+
let maxHigh = first.close;
|
|
44188
|
+
let totalVolume = 0;
|
|
44189
|
+
let lastOpenInterest = null;
|
|
44190
|
+
for (const candle of candles) {
|
|
44191
|
+
const low = candle.low ?? candle.close;
|
|
44192
|
+
const high = candle.high ?? candle.close;
|
|
44193
|
+
if (low !== null && low < minLow) minLow = low;
|
|
44194
|
+
if (high !== null && high > maxHigh) maxHigh = high;
|
|
44195
|
+
if (candle.volume !== null) totalVolume += candle.volume;
|
|
44196
|
+
if (candle.open_interest !== null) lastOpenInterest = candle.open_interest;
|
|
44197
|
+
}
|
|
44198
|
+
const change = round4(last.close - first.close);
|
|
44199
|
+
return {
|
|
44200
|
+
candles: candles.length,
|
|
44201
|
+
candles_with_trades: traded.length,
|
|
44202
|
+
first_at: first.end_time,
|
|
44203
|
+
last_at: last.end_time,
|
|
44204
|
+
first_close: first.close,
|
|
44205
|
+
first_close_cents: dollarsToCents(first.close),
|
|
44206
|
+
last_close: last.close,
|
|
44207
|
+
last_close_cents: dollarsToCents(last.close),
|
|
44208
|
+
min_low: minLow,
|
|
44209
|
+
max_high: maxHigh,
|
|
44210
|
+
change,
|
|
44211
|
+
change_pct: first.close > 0 ? Number((change / first.close * 100).toFixed(2)) : null,
|
|
44212
|
+
total_volume: Number(totalVolume.toFixed(2)),
|
|
44213
|
+
last_open_interest: lastOpenInterest
|
|
44214
|
+
};
|
|
44215
|
+
}
|
|
44216
|
+
function readCursor(record) {
|
|
44217
|
+
const cursor = record ? optionalString2(record.cursor) : null;
|
|
44218
|
+
return cursor;
|
|
44219
|
+
}
|
|
44220
|
+
async function fetchMarketsPage(params) {
|
|
44221
|
+
const url = buildUrl3("/markets", params);
|
|
44222
|
+
const data = await fetchKalshiJson(url, "GET /markets");
|
|
44223
|
+
const record = asRecord2(data);
|
|
44224
|
+
return {
|
|
44225
|
+
url,
|
|
44226
|
+
rows: asRecordArray2(record?.markets, "GET /markets"),
|
|
44227
|
+
nextCursor: readCursor(record)
|
|
44228
|
+
};
|
|
44229
|
+
}
|
|
44230
|
+
async function searchMarkets(filters) {
|
|
44231
|
+
const pageSize = filters.query ? SEARCH_PAGE_SIZE : filters.limit;
|
|
44232
|
+
const maxPages = filters.query ? SEARCH_MAX_PAGES : 1;
|
|
44233
|
+
let cursor = filters.cursor;
|
|
44234
|
+
let firstUrl = "";
|
|
44235
|
+
let scanned = 0;
|
|
44236
|
+
let pagesScanned = 0;
|
|
44237
|
+
const matched = [];
|
|
44238
|
+
for (let page = 0; page < maxPages; page++) {
|
|
44239
|
+
const { url, rows, nextCursor } = await fetchMarketsPage({
|
|
44240
|
+
status: filters.status ?? void 0,
|
|
44241
|
+
series_ticker: filters.seriesTicker ?? void 0,
|
|
44242
|
+
event_ticker: filters.eventTicker ?? void 0,
|
|
44243
|
+
mve_filter: filters.eventTicker ? void 0 : "exclude",
|
|
44244
|
+
limit: pageSize,
|
|
44245
|
+
cursor: cursor ?? void 0
|
|
44246
|
+
});
|
|
44247
|
+
if (!firstUrl) firstUrl = url;
|
|
44248
|
+
pagesScanned += 1;
|
|
44249
|
+
scanned += rows.length;
|
|
44250
|
+
for (const raw of rows) {
|
|
44251
|
+
const row = toMarketRow3(raw);
|
|
44252
|
+
if (!filters.query || matchesQuery2(
|
|
44253
|
+
[
|
|
44254
|
+
row.ticker,
|
|
44255
|
+
row.event_ticker,
|
|
44256
|
+
row.title,
|
|
44257
|
+
row.yes_sub_title,
|
|
44258
|
+
row.no_sub_title
|
|
44259
|
+
],
|
|
44260
|
+
filters.query
|
|
44261
|
+
)) {
|
|
44262
|
+
matched.push(row);
|
|
44263
|
+
}
|
|
44264
|
+
}
|
|
44265
|
+
cursor = nextCursor;
|
|
44266
|
+
if (!cursor) break;
|
|
44267
|
+
if (matched.length >= filters.limit) break;
|
|
44268
|
+
}
|
|
44269
|
+
return {
|
|
44270
|
+
url: firstUrl,
|
|
44271
|
+
rows: matched.slice(0, filters.limit),
|
|
44272
|
+
pageSize,
|
|
44273
|
+
pagesScanned,
|
|
44274
|
+
scanned,
|
|
44275
|
+
matched: matched.length,
|
|
44276
|
+
nextCursor: cursor,
|
|
44277
|
+
mayHaveMore: matched.length > filters.limit || cursor !== null
|
|
44278
|
+
};
|
|
44279
|
+
}
|
|
44280
|
+
async function fetchMarket(ticker) {
|
|
44281
|
+
const url = buildUrl3(`/markets/${encodeURIComponent(ticker)}`);
|
|
44282
|
+
try {
|
|
44283
|
+
const data = await fetchKalshiJson(url, "GET /markets/{ticker}");
|
|
44284
|
+
const record = asRecord2(asRecord2(data)?.market);
|
|
44285
|
+
if (!record || typeof record.ticker !== "string") {
|
|
44286
|
+
throw new Error(
|
|
44287
|
+
"Kalshi GET /markets/{ticker} returned an unexpected payload shape."
|
|
44288
|
+
);
|
|
44289
|
+
}
|
|
44290
|
+
return { url, market: normalizeMarket(record) };
|
|
44291
|
+
} catch (error) {
|
|
44292
|
+
if (error instanceof KalshiNotFoundError) {
|
|
44293
|
+
invalid2(
|
|
44294
|
+
`no Kalshi market has ticker "${ticker}". Tickers are uppercase strings like "KXBTCD-26SEP1612-T85799.99"; find one with kalshi_search_markets or kalshi_get_events.`
|
|
44295
|
+
);
|
|
44296
|
+
}
|
|
44297
|
+
throw error;
|
|
44298
|
+
}
|
|
44299
|
+
}
|
|
44300
|
+
async function fetchEventsPage(params) {
|
|
44301
|
+
const url = buildUrl3("/events", {
|
|
44302
|
+
status: params.status ?? void 0,
|
|
44303
|
+
series_ticker: params.seriesTicker ?? void 0,
|
|
44304
|
+
limit: params.limit,
|
|
44305
|
+
cursor: params.cursor ?? void 0,
|
|
44306
|
+
with_nested_markets: params.withNested ? true : void 0
|
|
44307
|
+
});
|
|
44308
|
+
const data = await fetchKalshiJson(url, "GET /events");
|
|
44309
|
+
const record = asRecord2(data);
|
|
44310
|
+
const rows = asRecordArray2(record?.events, "GET /events").map(
|
|
44311
|
+
(raw) => toEventRow2(raw, params.withNested)
|
|
44312
|
+
);
|
|
44313
|
+
return { url, rows, nextCursor: readCursor(record) };
|
|
44314
|
+
}
|
|
44315
|
+
async function fetchEvent(eventTicker, withNested) {
|
|
44316
|
+
const url = buildUrl3(`/events/${encodeURIComponent(eventTicker)}`, {
|
|
44317
|
+
with_nested_markets: withNested ? true : void 0
|
|
44318
|
+
});
|
|
44319
|
+
try {
|
|
44320
|
+
const data = await fetchKalshiJson(url, "GET /events/{event_ticker}");
|
|
44321
|
+
const record = asRecord2(data);
|
|
44322
|
+
const event = asRecord2(record?.event);
|
|
44323
|
+
if (!event || typeof event.event_ticker !== "string") {
|
|
44324
|
+
throw new Error(
|
|
44325
|
+
"Kalshi GET /events/{event_ticker} returned an unexpected payload shape."
|
|
44326
|
+
);
|
|
44327
|
+
}
|
|
44328
|
+
return { url, event: toEventRow2(event, withNested, record?.markets) };
|
|
44329
|
+
} catch (error) {
|
|
44330
|
+
if (error instanceof KalshiNotFoundError) {
|
|
44331
|
+
invalid2(
|
|
44332
|
+
`no Kalshi event has ticker "${eventTicker}". Event tickers look like "KXBTCD-26SEP1612" (series + date suffix); list them with kalshi_get_events and a series_ticker.`
|
|
44333
|
+
);
|
|
44334
|
+
}
|
|
44335
|
+
throw error;
|
|
44336
|
+
}
|
|
44337
|
+
}
|
|
44338
|
+
async function fetchOrderbook2(ticker, depth) {
|
|
44339
|
+
const url = buildUrl3(`/markets/${encodeURIComponent(ticker)}/orderbook`);
|
|
44340
|
+
try {
|
|
44341
|
+
const data = await fetchKalshiJson(
|
|
44342
|
+
url,
|
|
44343
|
+
"GET /markets/{ticker}/orderbook"
|
|
44344
|
+
);
|
|
44345
|
+
const record = asRecord2(data);
|
|
44346
|
+
if (!record || !asRecord2(record.orderbook_fp) && !asRecord2(record.orderbook)) {
|
|
44347
|
+
throw new Error(
|
|
44348
|
+
"Kalshi GET /markets/{ticker}/orderbook returned an unexpected payload shape."
|
|
44349
|
+
);
|
|
44350
|
+
}
|
|
44351
|
+
return { url, book: summarizeOrderbook2(record, ticker, depth) };
|
|
44352
|
+
} catch (error) {
|
|
44353
|
+
if (error instanceof KalshiNotFoundError) {
|
|
44354
|
+
invalid2(
|
|
44355
|
+
`no Kalshi market has ticker "${ticker}" (order book lookup returned 404). Find tickers with kalshi_search_markets.`
|
|
44356
|
+
);
|
|
44357
|
+
}
|
|
44358
|
+
throw error;
|
|
44359
|
+
}
|
|
44360
|
+
}
|
|
44361
|
+
async function fetchTrades(ticker, limit, cursor) {
|
|
44362
|
+
const url = buildUrl3("/markets/trades", {
|
|
44363
|
+
ticker,
|
|
44364
|
+
limit,
|
|
44365
|
+
cursor: cursor ?? void 0
|
|
44366
|
+
});
|
|
44367
|
+
const data = await fetchKalshiJson(url, "GET /markets/trades");
|
|
44368
|
+
const record = asRecord2(data);
|
|
44369
|
+
return {
|
|
44370
|
+
url,
|
|
44371
|
+
rows: asRecordArray2(record?.trades, "GET /markets/trades").map(toTradeRow),
|
|
44372
|
+
nextCursor: readCursor(record)
|
|
44373
|
+
};
|
|
44374
|
+
}
|
|
44375
|
+
async function assertMarketExists(ticker) {
|
|
44376
|
+
await fetchMarket(ticker);
|
|
44377
|
+
}
|
|
44378
|
+
async function fetchCandlesticks(seriesTicker, ticker, window) {
|
|
44379
|
+
const url = buildUrl3(
|
|
44380
|
+
`/series/${encodeURIComponent(seriesTicker)}/markets/${encodeURIComponent(ticker)}/candlesticks`,
|
|
44381
|
+
{
|
|
44382
|
+
start_ts: window.start_ts,
|
|
44383
|
+
end_ts: window.end_ts,
|
|
44384
|
+
period_interval: window.period_interval
|
|
44385
|
+
}
|
|
44386
|
+
);
|
|
44387
|
+
try {
|
|
44388
|
+
const data = await fetchKalshiJson(url, "GET candlesticks");
|
|
44389
|
+
const record = asRecord2(data);
|
|
44390
|
+
const candles = asRecordArray2(record?.candlesticks, "GET candlesticks").map(toCandle).filter((candle) => candle !== null).sort((a, b) => a.end_period_ts - b.end_period_ts);
|
|
44391
|
+
return { url, candles };
|
|
44392
|
+
} catch (error) {
|
|
44393
|
+
if (error instanceof KalshiNotFoundError) {
|
|
44394
|
+
invalid2(
|
|
44395
|
+
`no Kalshi candlesticks for ticker "${ticker}" under series "${seriesTicker}" (404). Verify the ticker with kalshi_get_market; if its series prefix is unusual, pass series_ticker explicitly.`
|
|
44396
|
+
);
|
|
44397
|
+
}
|
|
44398
|
+
throw error;
|
|
44399
|
+
}
|
|
44400
|
+
}
|
|
44401
|
+
|
|
44402
|
+
// src/tools/kalshi/tools.ts
|
|
44403
|
+
var PROVIDER_NAME3 = "Kalshi";
|
|
44404
|
+
var KALSHI_ODDS_NOTE = "Kalshi prices are market-implied odds (the 0-1 dollar price of a $1-payout contract, roughly the crowd's probability; cents = price x 100), not ground truth.";
|
|
44405
|
+
var KALSHI_ATTRIBUTION = "Source: Kalshi public API";
|
|
44406
|
+
var PRICE_UNITS_DOC = "Prices carry two units for one quote: the 0-1 field is dollars per $1 contract (= implied probability) and the *_cents field is the same quote in cents (sub-penny markets keep decimals, e.g. 99.49).";
|
|
44407
|
+
var ENVELOPE_DOC2 = `Every response carries { "provider": "Kalshi", "runtime_status", "fetched_at", "note" (odds caveat + "${KALSHI_ATTRIBUTION}" + request URL), "source_url", ..., "returnedCount", "may_have_more" }.`;
|
|
44408
|
+
function okResult2(sourceUrl, body) {
|
|
44409
|
+
const { returnedCount, may_have_more, ...rest } = body;
|
|
44410
|
+
return asTextContentResult({
|
|
44411
|
+
provider: PROVIDER_NAME3,
|
|
44412
|
+
runtime_status: "ok",
|
|
44413
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
44414
|
+
note: `${KALSHI_ODDS_NOTE} ${KALSHI_ATTRIBUTION} (${sourceUrl}).`,
|
|
44415
|
+
source_url: sourceUrl,
|
|
44416
|
+
...rest,
|
|
44417
|
+
returnedCount,
|
|
44418
|
+
may_have_more
|
|
44419
|
+
});
|
|
44420
|
+
}
|
|
44421
|
+
function errorResult4(tool101, error) {
|
|
44422
|
+
if (error instanceof KalshiInputError) {
|
|
44423
|
+
return asTextContentResult(
|
|
44424
|
+
{
|
|
44425
|
+
error: error.message,
|
|
44426
|
+
runtime_status: "invalid_input",
|
|
44427
|
+
provider: PROVIDER_NAME3,
|
|
44428
|
+
tool: tool101
|
|
44429
|
+
},
|
|
44430
|
+
true
|
|
44431
|
+
);
|
|
44432
|
+
}
|
|
44433
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44434
|
+
return asTextContentResult(
|
|
44435
|
+
{
|
|
44436
|
+
error: message,
|
|
44437
|
+
runtime_status: classifyRuntimeStatusFromError(message),
|
|
44438
|
+
provider: PROVIDER_NAME3,
|
|
44439
|
+
tool: tool101
|
|
44440
|
+
},
|
|
44441
|
+
true
|
|
44442
|
+
);
|
|
44443
|
+
}
|
|
44444
|
+
var TICKER_SCHEMA = {
|
|
44445
|
+
type: "string",
|
|
44446
|
+
description: 'Kalshi market ticker (uppercase, SERIES-EVENT-MARKET), e.g. "KXNIREFCALL-30JAN01-YES". Find tickers with kalshi_search_markets or kalshi_get_events.'
|
|
44447
|
+
};
|
|
44448
|
+
var SERIES_TICKER_SCHEMA = {
|
|
44449
|
+
type: "string",
|
|
44450
|
+
description: 'Series ticker filter, e.g. "KXBTCD" (hourly BTC price), "KXFEDDECISION" (Fed decisions), "KXHIGHNY" (NYC high temperature). The series is the leading segment of any ticker.'
|
|
44451
|
+
};
|
|
44452
|
+
var CURSOR_SCHEMA2 = {
|
|
44453
|
+
type: "string",
|
|
44454
|
+
description: "Opaque next_cursor from a previous response; pass it to continue paging."
|
|
44455
|
+
};
|
|
44456
|
+
var LIMIT_SCHEMA2 = {
|
|
44457
|
+
type: "integer",
|
|
44458
|
+
minimum: 1,
|
|
44459
|
+
maximum: MAX_LIMIT4,
|
|
44460
|
+
description: `Maximum rows to return (1-${MAX_LIMIT4}, default ${DEFAULT_LIMIT4}).`
|
|
44461
|
+
};
|
|
44462
|
+
function getAllKalshiEndpoints() {
|
|
44463
|
+
return [
|
|
44464
|
+
{
|
|
44465
|
+
metadata: {
|
|
44466
|
+
resource: "kalshi-markets-search",
|
|
44467
|
+
operation: "read",
|
|
44468
|
+
tags: ["kalshi", "prediction_markets", "markets", "search", "odds"]
|
|
44469
|
+
},
|
|
44470
|
+
tool: {
|
|
44471
|
+
name: "kalshi_search_markets",
|
|
44472
|
+
description: `Search Kalshi prediction markets by free text and list their market-implied odds (yes bid/ask, last price), volume, open interest, close time, and status (keyless public API). Kalshi has no server-side text search, so with a query the tool scans up to ${SEARCH_MAX_PAGES} pages of ${SEARCH_PAGE_SIZE} markets (combo/multivariate markets excluded) and keeps rows where every query term appears in the ticker, event ticker, title, or yes/no subtitle. Narrow with series_ticker or event_ticker whenever you can; the unfiltered scan covers only the newest few hundred of tens of thousands of open markets. Without a query it lists the first page of matching markets.
|
|
44473
|
+
|
|
44474
|
+
${PRICE_UNITS_DOC}
|
|
44475
|
+
|
|
44476
|
+
Args:
|
|
44477
|
+
- query (string, optional): free-text terms, all must match (case-insensitive), e.g. "btc" or "fed decision". Titles use Kalshi wording (e.g. "BTC", not "Bitcoin").
|
|
44478
|
+
- status (string, optional): listing filter, one of ${[...MARKET_STATUS_FILTERS, ANY_STATUS].map((item) => `"${item}"`).join(", ")} (default "open"; "all" = every status). Returned rows carry Kalshi's lifecycle status ("active" for open markets).
|
|
44479
|
+
- series_ticker (string, optional): restrict to one series, e.g. "KXBTCD".
|
|
44480
|
+
- event_ticker (string, optional): restrict to one event, e.g. "KXBTCD-26SEP1612".
|
|
44481
|
+
- limit (integer, optional): max rows (1-${MAX_LIMIT4}, default ${DEFAULT_LIMIT4}).
|
|
44482
|
+
- cursor (string, optional): next_cursor from a previous response to continue the scan.
|
|
44483
|
+
|
|
44484
|
+
Returns:
|
|
44485
|
+
${ENVELOPE_DOC2}
|
|
44486
|
+
"filters", "scan" { "page_size", "pages_scanned", "max_pages", "scanned", "matched" }, "next_cursor" (null when the listing is exhausted), and "rows": [{ "ticker", "event_ticker", "title", "yes_sub_title", "no_sub_title", "status", "market_type", "yes_bid", "yes_bid_cents", "yes_ask", "yes_ask_cents", "last_price", "last_price_cents", "volume", "volume_24h", "open_interest" (contracts), "open_time", "close_time" }].
|
|
44487
|
+
may_have_more is true when more rows matched than were returned or unscanned pages remain; pass next_cursor to continue, or add series_ticker / event_ticker.
|
|
44488
|
+
|
|
44489
|
+
Examples:
|
|
44490
|
+
- { "query": "btc", "series_ticker": "KXBTCD", "limit": 5 } \u2014 the live hourly BTC price brackets.
|
|
44491
|
+
- { "series_ticker": "KXFEDDECISION" } \u2014 every open Fed-decision market.
|
|
44492
|
+
- { "query": "referendum", "limit": 10 } \u2014 scan the newest open markets for referendum questions.
|
|
44493
|
+
- { "event_ticker": "KXBTCD-26SEP1612", "status": "all" } \u2014 every bracket of one event regardless of status.
|
|
44494
|
+
|
|
44495
|
+
Use when:
|
|
44496
|
+
- "What are the odds of X on Kalshi?" \u2014 find the market and read yes_bid / yes_ask / last_price
|
|
44497
|
+
- You need a ticker before an order-book, trades, or price-history read
|
|
44498
|
+
- You want every bracket of a series or event with current odds
|
|
44499
|
+
|
|
44500
|
+
Don't use when:
|
|
44501
|
+
- You already have a ticker (use kalshi_get_market)
|
|
44502
|
+
- You want the headline questions grouped by event (use kalshi_get_events)
|
|
44503
|
+
- You need Polymarket or Hyperliquid HIP-4 outcome markets (use their own tools)`,
|
|
44504
|
+
inputSchema: {
|
|
44505
|
+
type: "object",
|
|
44506
|
+
properties: {
|
|
44507
|
+
query: {
|
|
44508
|
+
type: "string",
|
|
44509
|
+
description: 'Free-text search terms; every term must appear in the ticker, event ticker, title, or yes/no subtitle (case-insensitive). Example: "btc".'
|
|
44510
|
+
},
|
|
44511
|
+
status: {
|
|
44512
|
+
type: "string",
|
|
44513
|
+
enum: [...MARKET_STATUS_FILTERS, ANY_STATUS],
|
|
44514
|
+
description: 'Listing status filter (default "open"). "all" returns every status.'
|
|
44515
|
+
},
|
|
44516
|
+
series_ticker: SERIES_TICKER_SCHEMA,
|
|
44517
|
+
event_ticker: {
|
|
44518
|
+
type: "string",
|
|
44519
|
+
description: 'Event ticker filter, e.g. "KXBTCD-26SEP1612" (one event, all of its markets).'
|
|
44520
|
+
},
|
|
44521
|
+
limit: LIMIT_SCHEMA2,
|
|
44522
|
+
cursor: CURSOR_SCHEMA2
|
|
44523
|
+
}
|
|
44524
|
+
},
|
|
44525
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44526
|
+
},
|
|
44527
|
+
handler: async (_client, args) => {
|
|
44528
|
+
const toolName = "kalshi_search_markets";
|
|
44529
|
+
try {
|
|
44530
|
+
const query = parseOptionalQuery2(args?.query);
|
|
44531
|
+
const status = parseStatusFilter(
|
|
44532
|
+
args?.status,
|
|
44533
|
+
MARKET_STATUS_FILTERS,
|
|
44534
|
+
"open"
|
|
44535
|
+
);
|
|
44536
|
+
const seriesTicker = parseOptionalTicker(
|
|
44537
|
+
args?.series_ticker,
|
|
44538
|
+
"series_ticker"
|
|
44539
|
+
);
|
|
44540
|
+
const eventTicker = parseOptionalTicker(
|
|
44541
|
+
args?.event_ticker,
|
|
44542
|
+
"event_ticker"
|
|
44543
|
+
);
|
|
44544
|
+
const limit = parseLimit2(args?.limit);
|
|
44545
|
+
const cursor = parseOptionalCursor(args?.cursor);
|
|
44546
|
+
const result2 = await searchMarkets({
|
|
44547
|
+
query,
|
|
44548
|
+
status,
|
|
44549
|
+
seriesTicker,
|
|
44550
|
+
eventTicker,
|
|
44551
|
+
limit,
|
|
44552
|
+
cursor
|
|
44553
|
+
});
|
|
44554
|
+
return okResult2(result2.url, {
|
|
44555
|
+
filters: {
|
|
44556
|
+
query,
|
|
44557
|
+
status: status ?? ANY_STATUS,
|
|
44558
|
+
series_ticker: seriesTicker,
|
|
44559
|
+
event_ticker: eventTicker,
|
|
44560
|
+
limit,
|
|
44561
|
+
cursor
|
|
44562
|
+
},
|
|
44563
|
+
scan: {
|
|
44564
|
+
page_size: result2.pageSize,
|
|
44565
|
+
pages_scanned: result2.pagesScanned,
|
|
44566
|
+
max_pages: query ? SEARCH_MAX_PAGES : 1,
|
|
44567
|
+
scanned: result2.scanned,
|
|
44568
|
+
matched: result2.matched
|
|
44569
|
+
},
|
|
44570
|
+
next_cursor: result2.nextCursor,
|
|
44571
|
+
rows: result2.rows,
|
|
44572
|
+
returnedCount: result2.rows.length,
|
|
44573
|
+
may_have_more: result2.mayHaveMore
|
|
44574
|
+
});
|
|
44575
|
+
} catch (error) {
|
|
44576
|
+
return errorResult4(toolName, error);
|
|
44577
|
+
}
|
|
44578
|
+
}
|
|
44579
|
+
},
|
|
44580
|
+
{
|
|
44581
|
+
metadata: {
|
|
44582
|
+
resource: "kalshi-events",
|
|
44583
|
+
operation: "read",
|
|
44584
|
+
tags: ["kalshi", "prediction_markets", "events", "odds"]
|
|
44585
|
+
},
|
|
44586
|
+
tool: {
|
|
44587
|
+
name: "kalshi_get_events",
|
|
44588
|
+
description: `List Kalshi EVENTS (a headline question grouping one or more markets, e.g. "BTC price on Sep 16, 2026 at 12pm EDT?" with one market per price bracket) with their series, category, strike date, and settlement sources (keyless public API). Filter by status and series_ticker, or fetch one event by event_ticker. Set with_nested_markets to include each event's market_count and up to ${EVENT_MARKETS_PREVIEW2} compact market rows with current odds.
|
|
44589
|
+
|
|
44590
|
+
${PRICE_UNITS_DOC}
|
|
44591
|
+
|
|
44592
|
+
Args:
|
|
44593
|
+
- event_ticker (string, optional): one event's ticker (exact), e.g. "KXBTCD-26SEP1612"; other filters are ignored.
|
|
44594
|
+
- status (string, optional): one of ${[...EVENT_STATUS_FILTERS, ANY_STATUS].map((item) => `"${item}"`).join(", ")} (default "open"; "all" = every status).
|
|
44595
|
+
- series_ticker (string, optional): restrict to one series, e.g. "KXFEDDECISION".
|
|
44596
|
+
- with_nested_markets (boolean, optional): include nested markets (default false). Slower for large pages.
|
|
44597
|
+
- limit (integer, optional): max events (1-${MAX_LIMIT4}, default ${DEFAULT_LIMIT4}).
|
|
44598
|
+
- cursor (string, optional): next_cursor from a previous response.
|
|
44599
|
+
|
|
44600
|
+
Returns:
|
|
44601
|
+
${ENVELOPE_DOC2}
|
|
44602
|
+
"filters", "next_cursor", and "rows": [{ "event_ticker", "series_ticker", "title", "sub_title", "category", "mutually_exclusive", "strike_date", "strike_period", "collateral_return_type", "settlement_sources" (names), "last_updated_ts", "market_count" (null unless nested), "markets" (compact market rows as in kalshi_search_markets, or null), "markets_truncated" }].
|
|
44603
|
+
|
|
44604
|
+
Examples:
|
|
44605
|
+
- { "series_ticker": "KXFEDDECISION", "with_nested_markets": true, "limit": 3 } \u2014 upcoming Fed meetings with their rate brackets.
|
|
44606
|
+
- { "event_ticker": "KXNIREFCALL-30JAN01", "with_nested_markets": true } \u2014 one event and its markets.
|
|
44607
|
+
- { "status": "open", "limit": 20 } \u2014 a page of open events across every category.
|
|
44608
|
+
|
|
44609
|
+
Use when:
|
|
44610
|
+
- "What is Kalshi pricing for the election / the Fed / a game?" \u2014 multi-market questions
|
|
44611
|
+
- You want to browse a series' upcoming events before picking a market
|
|
44612
|
+
- You need every bracket ticker of one event
|
|
44613
|
+
|
|
44614
|
+
Don't use when:
|
|
44615
|
+
- You want free-text search across markets (use kalshi_search_markets)
|
|
44616
|
+
- You need one market's full detail (use kalshi_get_market)`,
|
|
44617
|
+
inputSchema: {
|
|
44618
|
+
type: "object",
|
|
44619
|
+
properties: {
|
|
44620
|
+
event_ticker: {
|
|
44621
|
+
type: "string",
|
|
44622
|
+
description: 'Exact event ticker, e.g. "KXBTCD-26SEP1612". When set, other filters are ignored.'
|
|
44623
|
+
},
|
|
44624
|
+
status: {
|
|
44625
|
+
type: "string",
|
|
44626
|
+
enum: [...EVENT_STATUS_FILTERS, ANY_STATUS],
|
|
44627
|
+
description: 'Event status filter (default "open"). "all" returns every status.'
|
|
44628
|
+
},
|
|
44629
|
+
series_ticker: SERIES_TICKER_SCHEMA,
|
|
44630
|
+
with_nested_markets: {
|
|
44631
|
+
type: "boolean",
|
|
44632
|
+
description: `Include each event's markets (market_count plus up to ${EVENT_MARKETS_PREVIEW2} compact rows). Default false.`
|
|
44633
|
+
},
|
|
44634
|
+
limit: LIMIT_SCHEMA2,
|
|
44635
|
+
cursor: CURSOR_SCHEMA2
|
|
44636
|
+
}
|
|
44637
|
+
},
|
|
44638
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44639
|
+
},
|
|
44640
|
+
handler: async (_client, args) => {
|
|
44641
|
+
const toolName = "kalshi_get_events";
|
|
44642
|
+
try {
|
|
44643
|
+
const eventTicker = parseOptionalTicker(
|
|
44644
|
+
args?.event_ticker,
|
|
44645
|
+
"event_ticker"
|
|
44646
|
+
);
|
|
44647
|
+
const withNested = parseOptionalBoolean2(
|
|
44648
|
+
args?.with_nested_markets,
|
|
44649
|
+
"with_nested_markets",
|
|
44650
|
+
false
|
|
44651
|
+
);
|
|
44652
|
+
if (eventTicker) {
|
|
44653
|
+
const { url: url2, event } = await fetchEvent(eventTicker, withNested);
|
|
44654
|
+
return okResult2(url2, {
|
|
44655
|
+
filters: { event_ticker: eventTicker, with_nested_markets: withNested },
|
|
44656
|
+
next_cursor: null,
|
|
44657
|
+
rows: [event],
|
|
44658
|
+
returnedCount: 1,
|
|
44659
|
+
may_have_more: false
|
|
44660
|
+
});
|
|
44661
|
+
}
|
|
44662
|
+
const status = parseStatusFilter(
|
|
44663
|
+
args?.status,
|
|
44664
|
+
EVENT_STATUS_FILTERS,
|
|
44665
|
+
"open"
|
|
44666
|
+
);
|
|
44667
|
+
const seriesTicker = parseOptionalTicker(
|
|
44668
|
+
args?.series_ticker,
|
|
44669
|
+
"series_ticker"
|
|
44670
|
+
);
|
|
44671
|
+
const limit = parseLimit2(args?.limit);
|
|
44672
|
+
const cursor = parseOptionalCursor(args?.cursor);
|
|
44673
|
+
const { url, rows, nextCursor } = await fetchEventsPage({
|
|
44674
|
+
status,
|
|
44675
|
+
seriesTicker,
|
|
44676
|
+
limit,
|
|
44677
|
+
cursor,
|
|
44678
|
+
withNested
|
|
44679
|
+
});
|
|
44680
|
+
return okResult2(url, {
|
|
44681
|
+
filters: {
|
|
44682
|
+
status: status ?? ANY_STATUS,
|
|
44683
|
+
series_ticker: seriesTicker,
|
|
44684
|
+
with_nested_markets: withNested,
|
|
44685
|
+
limit,
|
|
44686
|
+
cursor
|
|
44687
|
+
},
|
|
44688
|
+
next_cursor: nextCursor,
|
|
44689
|
+
rows,
|
|
44690
|
+
returnedCount: rows.length,
|
|
44691
|
+
may_have_more: nextCursor !== null
|
|
44692
|
+
});
|
|
44693
|
+
} catch (error) {
|
|
44694
|
+
return errorResult4(toolName, error);
|
|
44695
|
+
}
|
|
44696
|
+
}
|
|
44697
|
+
},
|
|
44698
|
+
{
|
|
44699
|
+
metadata: {
|
|
44700
|
+
resource: "kalshi-market",
|
|
44701
|
+
operation: "read",
|
|
44702
|
+
tags: ["kalshi", "prediction_markets", "market", "odds"]
|
|
44703
|
+
},
|
|
44704
|
+
tool: {
|
|
44705
|
+
name: "kalshi_get_market",
|
|
44706
|
+
description: `Get one Kalshi market in full by ticker: title and yes/no subtitles, lifecycle status and result, yes/no bid and ask, last and previous price, midpoint and spread, volume, open interest, resting sizes, open/close/expiration times, strike fields, early-close terms, and the settlement rules (keyless public API).
|
|
44707
|
+
|
|
44708
|
+
${PRICE_UNITS_DOC}
|
|
44709
|
+
|
|
44710
|
+
Args:
|
|
44711
|
+
- ticker (string, required): market ticker, e.g. "KXNIREFCALL-30JAN01-YES".
|
|
44712
|
+
|
|
44713
|
+
Returns:
|
|
44714
|
+
${ENVELOPE_DOC2}
|
|
44715
|
+
"market": { "ticker", "event_ticker", "series_ticker" (derived from the ticker prefix), "title", "subtitle", "yes_sub_title", "no_sub_title", "status" (initialized|inactive|active|closed|determined|disputed|amended|finalized), "result" ("yes" | "no" | "" while open), "market_type" (binary|scalar), "yes_bid", "yes_bid_cents", "yes_ask", "yes_ask_cents", "no_bid", "no_bid_cents", "no_ask", "no_ask_cents", "last_price", "last_price_cents", "yes_midpoint", "yes_spread_cents", "previous_price", "previous_price_cents", "previous_yes_bid", "previous_yes_ask", "yes_bid_size", "yes_ask_size", "volume", "volume_24h", "open_interest", "liquidity_dollars", "notional_value_dollars", "open_time", "close_time", "expiration_time", "expected_expiration_time", "latest_expiration_time", "can_close_early", "early_close_condition", "settlement_timer_seconds", "strike_type", "floor_strike", "cap_strike", "custom_strike", "is_provisional", "mve_collection_ticker", "mve_selected_legs", "price_level_structure", "price_ranges", "rules_primary", "rules_secondary" (each cut at 1200 chars), "created_time", "updated_time" }.
|
|
44716
|
+
|
|
44717
|
+
Examples:
|
|
44718
|
+
- { "ticker": "KXNIREFCALL-30JAN01-YES" }
|
|
44719
|
+
- { "ticker": "KXBTCD-26SEP1612-T85799.99" }
|
|
44720
|
+
|
|
44721
|
+
Use when:
|
|
44722
|
+
- You have a ticker and need its full state, resolution rules, or close/expiration times
|
|
44723
|
+
- You are checking whether a market is still active before quoting its odds
|
|
44724
|
+
|
|
44725
|
+
Don't use when:
|
|
44726
|
+
- You only know the topic (use kalshi_search_markets)
|
|
44727
|
+
- You need bid/ask depth or the trade tape (use kalshi_get_orderbook / kalshi_get_trades)`,
|
|
44728
|
+
inputSchema: {
|
|
44729
|
+
type: "object",
|
|
44730
|
+
properties: { ticker: TICKER_SCHEMA },
|
|
44731
|
+
required: ["ticker"]
|
|
44732
|
+
},
|
|
44733
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44734
|
+
},
|
|
44735
|
+
handler: async (_client, args) => {
|
|
44736
|
+
const toolName = "kalshi_get_market";
|
|
44737
|
+
try {
|
|
44738
|
+
const ticker = parseRequiredTicker(args?.ticker);
|
|
44739
|
+
const { url, market } = await fetchMarket(ticker);
|
|
44740
|
+
return okResult2(url, {
|
|
44741
|
+
market,
|
|
44742
|
+
returnedCount: 1,
|
|
44743
|
+
may_have_more: false
|
|
44744
|
+
});
|
|
44745
|
+
} catch (error) {
|
|
44746
|
+
return errorResult4(toolName, error);
|
|
44747
|
+
}
|
|
44748
|
+
}
|
|
44749
|
+
},
|
|
44750
|
+
{
|
|
44751
|
+
metadata: {
|
|
44752
|
+
resource: "kalshi-orderbook",
|
|
44753
|
+
operation: "read",
|
|
44754
|
+
tags: ["kalshi", "prediction_markets", "orderbook", "liquidity", "odds"]
|
|
44755
|
+
},
|
|
44756
|
+
tool: {
|
|
44757
|
+
name: "kalshi_get_orderbook",
|
|
44758
|
+
description: `Get the live order book of one Kalshi market (keyless public API): resting YES bids and NO bids best-first to the requested depth, level totals, and the derived best yes/no ask, spread, and midpoint. Kalshi serves bids only because a NO bid at price p is a YES ask at 1 - p (binary contracts sum to $1); the derived ask fields apply that identity. Sizes are contracts (each pays $1 if its side wins).
|
|
44759
|
+
|
|
44760
|
+
${PRICE_UNITS_DOC}
|
|
44761
|
+
|
|
44762
|
+
Args:
|
|
44763
|
+
- ticker (string, required): market ticker.
|
|
44764
|
+
- depth (integer, optional): levels per side (1-${MAX_BOOK_DEPTH2}, default ${DEFAULT_BOOK_DEPTH2}).
|
|
44765
|
+
|
|
44766
|
+
Returns:
|
|
44767
|
+
${ENVELOPE_DOC2}
|
|
44768
|
+
"ticker", "depth", "yes_bids": [{ "price", "cents", "size" }] (highest first), "no_bids": [...] (highest first), "yes_levels_total", "no_levels_total", "best_yes_bid", "best_yes_bid_cents", "best_yes_ask" (= 1 - best_no_bid), "best_yes_ask_cents", "best_no_bid", "best_no_ask" (= 1 - best_yes_bid), "yes_spread", "yes_midpoint". returnedCount is the number of levels returned across both sides. An empty book on a real market returns zero levels with runtime_status ok; an unknown ticker is rejected as invalid_input.
|
|
44769
|
+
|
|
44770
|
+
Examples:
|
|
44771
|
+
- { "ticker": "KXNIREFCALL-30JAN01-YES", "depth": 5 }
|
|
44772
|
+
|
|
44773
|
+
Use when:
|
|
44774
|
+
- You need executable odds (bid/ask) and depth, not just the last trade, or want to size a hypothetical position
|
|
44775
|
+
- You are checking spread and depth before trusting a thin market's implied probability
|
|
44776
|
+
|
|
44777
|
+
Don't use when:
|
|
44778
|
+
- You want the market's headline odds and metadata (use kalshi_get_market)
|
|
44779
|
+
- You want a CEX or Hyperliquid order book (use get_orderbook / hyperliquid_get_l2_book)`,
|
|
44780
|
+
inputSchema: {
|
|
44781
|
+
type: "object",
|
|
44782
|
+
properties: {
|
|
44783
|
+
ticker: TICKER_SCHEMA,
|
|
44784
|
+
depth: {
|
|
44785
|
+
type: "integer",
|
|
44786
|
+
minimum: 1,
|
|
44787
|
+
maximum: MAX_BOOK_DEPTH2,
|
|
44788
|
+
description: `Levels per side (1-${MAX_BOOK_DEPTH2}, default ${DEFAULT_BOOK_DEPTH2}).`
|
|
44789
|
+
}
|
|
44790
|
+
},
|
|
44791
|
+
required: ["ticker"]
|
|
44792
|
+
},
|
|
44793
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44794
|
+
},
|
|
44795
|
+
handler: async (_client, args) => {
|
|
44796
|
+
const toolName = "kalshi_get_orderbook";
|
|
44797
|
+
try {
|
|
44798
|
+
const ticker = parseRequiredTicker(args?.ticker);
|
|
44799
|
+
const depth = parseBookDepth2(args?.depth);
|
|
44800
|
+
const { url, book } = await fetchOrderbook2(ticker, depth);
|
|
44801
|
+
if (book.yes_levels_total === 0 && book.no_levels_total === 0) {
|
|
44802
|
+
await assertMarketExists(ticker);
|
|
44803
|
+
}
|
|
44804
|
+
return okResult2(url, {
|
|
44805
|
+
...book,
|
|
44806
|
+
depth,
|
|
44807
|
+
returnedCount: book.yes_bids.length + book.no_bids.length,
|
|
44808
|
+
may_have_more: book.yes_levels_total > depth || book.no_levels_total > depth
|
|
44809
|
+
});
|
|
44810
|
+
} catch (error) {
|
|
44811
|
+
return errorResult4(toolName, error);
|
|
44812
|
+
}
|
|
44813
|
+
}
|
|
44814
|
+
},
|
|
44815
|
+
{
|
|
44816
|
+
metadata: {
|
|
44817
|
+
resource: "kalshi-trades",
|
|
44818
|
+
operation: "read",
|
|
44819
|
+
tags: ["kalshi", "prediction_markets", "trades", "tape", "odds"]
|
|
44820
|
+
},
|
|
44821
|
+
tool: {
|
|
44822
|
+
name: "kalshi_get_trades",
|
|
44823
|
+
description: `Get the recent trade tape of one Kalshi market (keyless public API), newest first: contracts traded, the yes and no price of each fill, the taker side, and whether it was a block trade. Paginate with next_cursor.
|
|
44824
|
+
|
|
44825
|
+
${PRICE_UNITS_DOC}
|
|
44826
|
+
|
|
44827
|
+
Args:
|
|
44828
|
+
- ticker (string, required): market ticker.
|
|
44829
|
+
- limit (integer, optional): max trades (1-${MAX_TRADES_LIMIT}, default ${DEFAULT_TRADES_LIMIT}).
|
|
44830
|
+
- cursor (string, optional): next_cursor from a previous response for older trades.
|
|
44831
|
+
|
|
44832
|
+
Returns:
|
|
44833
|
+
${ENVELOPE_DOC2}
|
|
44834
|
+
"ticker", "next_cursor", and "rows": [{ "trade_id", "ticker", "created_time", "count" (contracts), "yes_price", "yes_price_cents", "no_price", "no_price_cents", "taker_side" ("yes" | "no"), "taker_book_side" ("bid" | "ask"), "is_block_trade" }]. An empty tape on a real market returns zero rows with runtime_status ok; an unknown ticker is rejected as invalid_input.
|
|
44835
|
+
|
|
44836
|
+
Examples:
|
|
44837
|
+
- { "ticker": "KXNIREFCALL-30JAN01-YES", "limit": 20 }
|
|
44838
|
+
- { "ticker": "KXNIREFCALL-30JAN01-YES", "limit": 100, "cursor": "<next_cursor>" } \u2014 the next page of older fills.
|
|
44839
|
+
|
|
44840
|
+
Use when:
|
|
44841
|
+
- "Who is hitting the bid / lifting the offer on this market?" \u2014 recent flow and taker direction
|
|
44842
|
+
- You want fill prices and sizes rather than resting quotes
|
|
44843
|
+
|
|
44844
|
+
Don't use when:
|
|
44845
|
+
- You need resting liquidity (use kalshi_get_orderbook)
|
|
44846
|
+
- You need an OHLC series over time (use kalshi_get_price_history)`,
|
|
44847
|
+
inputSchema: {
|
|
44848
|
+
type: "object",
|
|
44849
|
+
properties: {
|
|
44850
|
+
ticker: TICKER_SCHEMA,
|
|
44851
|
+
limit: {
|
|
44852
|
+
type: "integer",
|
|
44853
|
+
minimum: 1,
|
|
44854
|
+
maximum: MAX_TRADES_LIMIT,
|
|
44855
|
+
description: `Maximum trades to return (1-${MAX_TRADES_LIMIT}, default ${DEFAULT_TRADES_LIMIT}).`
|
|
44856
|
+
},
|
|
44857
|
+
cursor: CURSOR_SCHEMA2
|
|
44858
|
+
},
|
|
44859
|
+
required: ["ticker"]
|
|
44860
|
+
},
|
|
44861
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44862
|
+
},
|
|
44863
|
+
handler: async (_client, args) => {
|
|
44864
|
+
const toolName = "kalshi_get_trades";
|
|
44865
|
+
try {
|
|
44866
|
+
const ticker = parseRequiredTicker(args?.ticker);
|
|
44867
|
+
const limit = parseTradesLimit(args?.limit);
|
|
44868
|
+
const cursor = parseOptionalCursor(args?.cursor);
|
|
44869
|
+
const { url, rows, nextCursor } = await fetchTrades(
|
|
44870
|
+
ticker,
|
|
44871
|
+
limit,
|
|
44872
|
+
cursor
|
|
44873
|
+
);
|
|
44874
|
+
if (rows.length === 0 && cursor === null) {
|
|
44875
|
+
await assertMarketExists(ticker);
|
|
44876
|
+
}
|
|
44877
|
+
return okResult2(url, {
|
|
44878
|
+
ticker,
|
|
44879
|
+
filters: { limit, cursor },
|
|
44880
|
+
next_cursor: nextCursor,
|
|
44881
|
+
rows,
|
|
44882
|
+
returnedCount: rows.length,
|
|
44883
|
+
may_have_more: nextCursor !== null
|
|
44884
|
+
});
|
|
44885
|
+
} catch (error) {
|
|
44886
|
+
return errorResult4(toolName, error);
|
|
44887
|
+
}
|
|
44888
|
+
}
|
|
44889
|
+
},
|
|
44890
|
+
{
|
|
44891
|
+
metadata: {
|
|
44892
|
+
resource: "kalshi-price-history",
|
|
44893
|
+
operation: "read",
|
|
44894
|
+
tags: [
|
|
44895
|
+
"kalshi",
|
|
44896
|
+
"prediction_markets",
|
|
44897
|
+
"price-history",
|
|
44898
|
+
"candlesticks",
|
|
44899
|
+
"odds",
|
|
44900
|
+
"timeseries"
|
|
44901
|
+
]
|
|
44902
|
+
},
|
|
44903
|
+
tool: {
|
|
44904
|
+
name: "kalshi_get_price_history",
|
|
44905
|
+
description: `Get candlestick price history for one Kalshi market (keyless public API): per period the trade-price open/high/low/close/mean, the previous close carried through quiet periods, the closing yes bid and ask, volume, and open interest, plus a summary (first/last close, range, change, total volume). Candles are 1-minute, 1-hour, or 1-day. At most ${MAX_CANDLES} candles are returned: a wider window is clamped to the most recent ${MAX_CANDLES} and flagged (window.truncated), so page back by passing an earlier end.
|
|
44906
|
+
|
|
44907
|
+
Candle prices are 0-1 dollars per $1 contract (= implied probability); the summary also carries cents. Periods without trades have null OHLC and a non-null "previous".
|
|
44908
|
+
|
|
44909
|
+
Args:
|
|
44910
|
+
- ticker (string, required): market ticker.
|
|
44911
|
+
- period_interval (integer, optional): minutes per candle, one of ${PERIOD_INTERVALS.join(", ")} (default ${DEFAULT_PERIOD_INTERVAL}).
|
|
44912
|
+
- start (string | integer, optional): ISO 8601 datetime or unix seconds. Defaults to a window of 480 candles (1m, 60m) or 365 candles (1440m) before end.
|
|
44913
|
+
- end (string | integer, optional): ISO 8601 datetime or unix seconds (default now).
|
|
44914
|
+
- series_ticker (string, optional): the market's series. Kalshi's candlestick route is keyed by series; by default it is derived from the ticker's leading segment (e.g. "KXBTCD" for "KXBTCD-26SEP1612-T85799.99"). Pass it only when that convention does not hold.
|
|
44915
|
+
|
|
44916
|
+
Returns:
|
|
44917
|
+
${ENVELOPE_DOC2}
|
|
44918
|
+
"ticker", "series_ticker", "series_ticker_source" ("argument" | "derived_from_ticker"), "period_interval", "window": { "start", "end", "requested_start", "truncated", "max_candles" }, "summary": { "candles", "candles_with_trades", "first_at", "last_at", "first_close", "first_close_cents", "last_close", "last_close_cents", "min_low", "max_high", "change", "change_pct", "total_volume", "last_open_interest" } (null when no candle traded), "candles": [{ "end_period_ts", "end_time", "open", "high", "low", "close", "mean", "previous", "yes_bid_close", "yes_ask_close", "volume", "open_interest" }] (oldest first). may_have_more is true when the window was clamped.
|
|
44919
|
+
|
|
44920
|
+
Examples:
|
|
44921
|
+
- { "ticker": "KXNIREFCALL-30JAN01-YES", "period_interval": 1440 } \u2014 one year of daily candles.
|
|
44922
|
+
- { "ticker": "KXBTCD-26SEP1612-T85799.99", "period_interval": 1, "start": "2026-09-16T12:00:00Z", "end": "2026-09-16T16:00:00Z" } \u2014 minute candles for one session.
|
|
44923
|
+
|
|
44924
|
+
Use when:
|
|
44925
|
+
- "How have the odds of X moved this week/month?" \u2014 trend, drawdown, or repricing questions
|
|
44926
|
+
- You want to time-align a market's odds with news or on-chain events
|
|
44927
|
+
|
|
44928
|
+
Don't use when:
|
|
44929
|
+
- You need the current bid/ask depth (use kalshi_get_orderbook)
|
|
44930
|
+
- You need crypto token price history (use get_coin_market_chart_range or CCXT OHLCV tools)`,
|
|
44931
|
+
inputSchema: {
|
|
44932
|
+
type: "object",
|
|
44933
|
+
properties: {
|
|
44934
|
+
ticker: TICKER_SCHEMA,
|
|
44935
|
+
period_interval: {
|
|
44936
|
+
type: "integer",
|
|
44937
|
+
enum: [...PERIOD_INTERVALS],
|
|
44938
|
+
description: `Minutes per candle: 1, 60, or 1440 (default ${DEFAULT_PERIOD_INTERVAL}).`
|
|
44939
|
+
},
|
|
44940
|
+
start: {
|
|
44941
|
+
type: "string",
|
|
44942
|
+
description: 'Window start as ISO 8601 (e.g. "2026-09-01T00:00:00Z") or unix seconds. Defaults to 480 candles (1m/60m) or 365 candles (1440m) before end.'
|
|
44943
|
+
},
|
|
44944
|
+
end: {
|
|
44945
|
+
type: "string",
|
|
44946
|
+
description: "Window end as ISO 8601 or unix seconds (default now). Pass an earlier end to page back in time."
|
|
44947
|
+
},
|
|
44948
|
+
series_ticker: {
|
|
44949
|
+
type: "string",
|
|
44950
|
+
description: `Series ticker override for the candlestick route. Defaults to the ticker's leading segment (e.g. "KXBTCD").`
|
|
44951
|
+
}
|
|
44952
|
+
},
|
|
44953
|
+
required: ["ticker"]
|
|
44954
|
+
},
|
|
44955
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44956
|
+
},
|
|
44957
|
+
handler: async (_client, args) => {
|
|
44958
|
+
const toolName = "kalshi_get_price_history";
|
|
44959
|
+
try {
|
|
44960
|
+
const ticker = parseRequiredTicker(args?.ticker);
|
|
44961
|
+
const explicitSeries = parseOptionalTicker(
|
|
44962
|
+
args?.series_ticker,
|
|
44963
|
+
"series_ticker"
|
|
44964
|
+
);
|
|
44965
|
+
const window = resolveCandleWindow(args);
|
|
44966
|
+
const seriesTicker = explicitSeries ?? seriesTickerFromTicker(ticker);
|
|
44967
|
+
const { url, candles } = await fetchCandlesticks(
|
|
44968
|
+
seriesTicker,
|
|
44969
|
+
ticker,
|
|
44970
|
+
window
|
|
44971
|
+
);
|
|
44972
|
+
const returned = candles.slice(-MAX_CANDLES);
|
|
44973
|
+
const clamped = window.window_truncated || returned.length < candles.length;
|
|
44974
|
+
return okResult2(url, {
|
|
44975
|
+
ticker,
|
|
44976
|
+
series_ticker: seriesTicker,
|
|
44977
|
+
series_ticker_source: explicitSeries ? "argument" : "derived_from_ticker",
|
|
44978
|
+
period_interval: window.period_interval,
|
|
44979
|
+
window: {
|
|
44980
|
+
start: new Date(window.start_ts * 1e3).toISOString(),
|
|
44981
|
+
end: new Date(window.end_ts * 1e3).toISOString(),
|
|
44982
|
+
requested_start: window.requested_start_ts !== null ? new Date(window.requested_start_ts * 1e3).toISOString() : null,
|
|
44983
|
+
truncated: clamped,
|
|
44984
|
+
max_candles: MAX_CANDLES
|
|
44985
|
+
},
|
|
44986
|
+
summary: summarizeCandles(returned),
|
|
44987
|
+
candles_total: candles.length,
|
|
44988
|
+
candles: returned,
|
|
44989
|
+
returnedCount: returned.length,
|
|
44990
|
+
may_have_more: clamped
|
|
44991
|
+
});
|
|
44992
|
+
} catch (error) {
|
|
44993
|
+
return errorResult4(toolName, error);
|
|
44994
|
+
}
|
|
44995
|
+
}
|
|
44996
|
+
}
|
|
44997
|
+
];
|
|
44998
|
+
}
|
|
44999
|
+
|
|
43831
45000
|
// src/tools/signals/http.ts
|
|
43832
45001
|
var SIGNALS_FETCH_TIMEOUT_MS = 1e4;
|
|
43833
45002
|
var SignalsInputError = class extends Error {
|
|
@@ -43886,13 +45055,13 @@ async function fetchJsonWithTimeout(url, label, timeoutMs = SIGNALS_FETCH_TIMEOU
|
|
|
43886
45055
|
clearTimeout(timer);
|
|
43887
45056
|
}
|
|
43888
45057
|
}
|
|
43889
|
-
function
|
|
45058
|
+
function parseFiniteNumber4(value) {
|
|
43890
45059
|
const num = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
43891
45060
|
return Number.isFinite(num) ? num : null;
|
|
43892
45061
|
}
|
|
43893
45062
|
function parseIntegerArg(value, name, min, max, fallback) {
|
|
43894
45063
|
if (value === void 0 || value === null) return fallback;
|
|
43895
|
-
const parsed =
|
|
45064
|
+
const parsed = parseFiniteNumber4(value);
|
|
43896
45065
|
if (parsed === null || !Number.isInteger(parsed) || parsed < min || parsed > max) {
|
|
43897
45066
|
throw new SignalsInputError(
|
|
43898
45067
|
`${name} must be an integer between ${min} and ${max}; got ${JSON.stringify(value)}.`
|
|
@@ -43902,7 +45071,7 @@ function parseIntegerArg(value, name, min, max, fallback) {
|
|
|
43902
45071
|
}
|
|
43903
45072
|
function parseNumberArg(value, name, min, max, fallback) {
|
|
43904
45073
|
if (value === void 0 || value === null) return fallback;
|
|
43905
|
-
const parsed =
|
|
45074
|
+
const parsed = parseFiniteNumber4(value);
|
|
43906
45075
|
if (parsed === null || parsed < min || parsed > max) {
|
|
43907
45076
|
throw new SignalsInputError(
|
|
43908
45077
|
`${name} must be a number between ${min} and ${max}; got ${JSON.stringify(value)}.`
|
|
@@ -44000,8 +45169,8 @@ function normalizeFearGreed(raw) {
|
|
|
44000
45169
|
let newestSeen = -Infinity;
|
|
44001
45170
|
for (const entry2 of body.data) {
|
|
44002
45171
|
const item = entry2 ?? {};
|
|
44003
|
-
const value =
|
|
44004
|
-
const unixTimestamp =
|
|
45172
|
+
const value = parseFiniteNumber4(item.value);
|
|
45173
|
+
const unixTimestamp = parseFiniteNumber4(item.timestamp);
|
|
44005
45174
|
if (value === null || unixTimestamp === null) continue;
|
|
44006
45175
|
history.push({
|
|
44007
45176
|
value,
|
|
@@ -44011,7 +45180,7 @@ function normalizeFearGreed(raw) {
|
|
|
44011
45180
|
});
|
|
44012
45181
|
if (unixTimestamp > newestSeen) {
|
|
44013
45182
|
newestSeen = unixTimestamp;
|
|
44014
|
-
timeUntilUpdate =
|
|
45183
|
+
timeUntilUpdate = parseFiniteNumber4(item.time_until_update);
|
|
44015
45184
|
}
|
|
44016
45185
|
}
|
|
44017
45186
|
history.sort((a, b) => b.unix_timestamp - a.unix_timestamp);
|
|
@@ -44214,7 +45383,7 @@ var COIN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,99}$/;
|
|
|
44214
45383
|
var VS_CURRENCY_RE = /^[a-z0-9]{2,10}$/;
|
|
44215
45384
|
function parseDays(value) {
|
|
44216
45385
|
if (value === void 0 || value === null) return DEFAULT_DAYS;
|
|
44217
|
-
const parsed =
|
|
45386
|
+
const parsed = parseFiniteNumber4(value);
|
|
44218
45387
|
const match = OHLC_DAYS.find((days) => days === parsed);
|
|
44219
45388
|
if (match === void 0) {
|
|
44220
45389
|
throw new SignalsInputError(
|
|
@@ -44322,7 +45491,7 @@ function parseCandles(raw) {
|
|
|
44322
45491
|
const byTime = /* @__PURE__ */ new Map();
|
|
44323
45492
|
for (const entry2 of raw) {
|
|
44324
45493
|
if (!Array.isArray(entry2) || entry2.length < 5) continue;
|
|
44325
|
-
const [time, open, high, low, close] = entry2.slice(0, 5).map(
|
|
45494
|
+
const [time, open, high, low, close] = entry2.slice(0, 5).map(parseFiniteNumber4);
|
|
44326
45495
|
if (time === null || open === null || high === null || low === null || close === null) {
|
|
44327
45496
|
continue;
|
|
44328
45497
|
}
|
|
@@ -44572,8 +45741,8 @@ function parseChart(raw, chart) {
|
|
|
44572
45741
|
const points = [];
|
|
44573
45742
|
for (const entry2 of body.values) {
|
|
44574
45743
|
const point = entry2 ?? {};
|
|
44575
|
-
const x =
|
|
44576
|
-
const y =
|
|
45744
|
+
const x = parseFiniteNumber4(point.x);
|
|
45745
|
+
const y = parseFiniteNumber4(point.y);
|
|
44577
45746
|
if (x === null || y === null) continue;
|
|
44578
45747
|
points.push({ day: new Date(x * 1e3).toISOString().slice(0, 10), value: y });
|
|
44579
45748
|
}
|
|
@@ -44665,11 +45834,11 @@ function parseHacksArgs(args) {
|
|
|
44665
45834
|
sinceMs
|
|
44666
45835
|
};
|
|
44667
45836
|
}
|
|
44668
|
-
function
|
|
45837
|
+
function optionalString3(value) {
|
|
44669
45838
|
return typeof value === "string" && value.trim() !== "" ? value : null;
|
|
44670
45839
|
}
|
|
44671
45840
|
function idString(value) {
|
|
44672
|
-
if (typeof value === "string") return
|
|
45841
|
+
if (typeof value === "string") return optionalString3(value);
|
|
44673
45842
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
44674
45843
|
return null;
|
|
44675
45844
|
}
|
|
@@ -44683,25 +45852,25 @@ function normalizeHackRows(raw) {
|
|
|
44683
45852
|
for (const entry2 of raw) {
|
|
44684
45853
|
if (!entry2 || typeof entry2 !== "object") continue;
|
|
44685
45854
|
const item = entry2;
|
|
44686
|
-
const timestamp =
|
|
44687
|
-
const name =
|
|
45855
|
+
const timestamp = parseFiniteNumber4(item.date);
|
|
45856
|
+
const name = optionalString3(item.name);
|
|
44688
45857
|
if (timestamp === null || name === null) continue;
|
|
44689
45858
|
const chains = Array.isArray(item.chain) ? item.chain.filter((chain) => typeof chain === "string") : typeof item.chain === "string" ? [item.chain] : [];
|
|
44690
45859
|
rows.push({
|
|
44691
45860
|
date: new Date(timestamp * 1e3).toISOString(),
|
|
44692
45861
|
timestamp,
|
|
44693
45862
|
name,
|
|
44694
|
-
classification:
|
|
44695
|
-
technique:
|
|
44696
|
-
amount_usd:
|
|
45863
|
+
classification: optionalString3(item.classification),
|
|
45864
|
+
technique: optionalString3(item.technique),
|
|
45865
|
+
amount_usd: parseFiniteNumber4(item.amount),
|
|
44697
45866
|
chains,
|
|
44698
45867
|
bridge_hack: item.bridgeHack === true,
|
|
44699
|
-
target_type:
|
|
44700
|
-
source:
|
|
44701
|
-
returned_funds_usd:
|
|
45868
|
+
target_type: optionalString3(item.targetType),
|
|
45869
|
+
source: optionalString3(item.source),
|
|
45870
|
+
returned_funds_usd: parseFiniteNumber4(item.returnedFunds),
|
|
44702
45871
|
defillama_id: idString(item.defillamaId),
|
|
44703
45872
|
parent_protocol_id: idString(item.parentProtocolId),
|
|
44704
|
-
language:
|
|
45873
|
+
language: optionalString3(item.language)
|
|
44705
45874
|
});
|
|
44706
45875
|
}
|
|
44707
45876
|
rows.sort(
|
|
@@ -44722,7 +45891,7 @@ async function fetchHackRows() {
|
|
|
44722
45891
|
}
|
|
44723
45892
|
|
|
44724
45893
|
// src/tools/signals/tools.ts
|
|
44725
|
-
function
|
|
45894
|
+
function errorResult5(tool101, error, extra = {}) {
|
|
44726
45895
|
if (error instanceof SignalsInputError) {
|
|
44727
45896
|
return asTextContentResult(
|
|
44728
45897
|
{ error: error.message, runtime_status: "invalid_input", tool: tool101, ...extra },
|
|
@@ -44800,7 +45969,7 @@ Don't use when:
|
|
|
44800
45969
|
may_have_more: history.length >= limit
|
|
44801
45970
|
});
|
|
44802
45971
|
} catch (error) {
|
|
44803
|
-
return
|
|
45972
|
+
return errorResult5(toolName, error);
|
|
44804
45973
|
}
|
|
44805
45974
|
}
|
|
44806
45975
|
},
|
|
@@ -44919,7 +46088,7 @@ Don't use when:
|
|
|
44919
46088
|
...computeTechnicalIndicators(candles, parsed)
|
|
44920
46089
|
});
|
|
44921
46090
|
} catch (error) {
|
|
44922
|
-
return
|
|
46091
|
+
return errorResult5(toolName, error);
|
|
44923
46092
|
}
|
|
44924
46093
|
}
|
|
44925
46094
|
},
|
|
@@ -45016,7 +46185,7 @@ Don't use when:
|
|
|
45016
46185
|
rows
|
|
45017
46186
|
});
|
|
45018
46187
|
} catch (error) {
|
|
45019
|
-
return
|
|
46188
|
+
return errorResult5(toolName, error);
|
|
45020
46189
|
}
|
|
45021
46190
|
}
|
|
45022
46191
|
},
|
|
@@ -45099,7 +46268,7 @@ Don't use when:
|
|
|
45099
46268
|
rows: page
|
|
45100
46269
|
});
|
|
45101
46270
|
} catch (error) {
|
|
45102
|
-
return
|
|
46271
|
+
return errorResult5(toolName, error);
|
|
45103
46272
|
}
|
|
45104
46273
|
}
|
|
45105
46274
|
}
|
|
@@ -45239,7 +46408,7 @@ function blockscoutGet(chain, path) {
|
|
|
45239
46408
|
}
|
|
45240
46409
|
var ownCoinGecko = null;
|
|
45241
46410
|
async function resolvePrice(chain, address, token) {
|
|
45242
|
-
const blockscoutRate =
|
|
46411
|
+
const blockscoutRate = parseFiniteNumber4(token.exchange_rate);
|
|
45243
46412
|
if (blockscoutRate !== null && blockscoutRate > 0) {
|
|
45244
46413
|
return { price: blockscoutRate, source: "blockscout_exchange_rate" };
|
|
45245
46414
|
}
|
|
@@ -45251,7 +46420,7 @@ async function resolvePrice(chain, address, token) {
|
|
|
45251
46420
|
{ network: chain.gecko }
|
|
45252
46421
|
);
|
|
45253
46422
|
const prices = result2?.data?.attributes?.token_prices ?? {};
|
|
45254
|
-
const price =
|
|
46423
|
+
const price = parseFiniteNumber4(
|
|
45255
46424
|
prices[address.toLowerCase()] ?? prices[address]
|
|
45256
46425
|
);
|
|
45257
46426
|
if (price !== null && price > 0) {
|
|
@@ -45270,11 +46439,11 @@ async function getTokenHolderDistribution(args, now = Date.now) {
|
|
|
45270
46439
|
`this address is a ${String(token.type)} token; holder tiers are computed for ERC-20 tokens only.`
|
|
45271
46440
|
);
|
|
45272
46441
|
}
|
|
45273
|
-
const decimals =
|
|
46442
|
+
const decimals = parseFiniteNumber4(token.decimals);
|
|
45274
46443
|
if (decimals === null) {
|
|
45275
46444
|
throw new Error("Blockscout returned no decimals for this token");
|
|
45276
46445
|
}
|
|
45277
|
-
const totalHolders =
|
|
46446
|
+
const totalHolders = parseFiniteNumber4(token.holders_count ?? token.holders);
|
|
45278
46447
|
const { price, source: priceSource } = priceOverride !== null ? { price: priceOverride, source: "caller" } : await resolvePrice(chain, address, token);
|
|
45279
46448
|
const base = {
|
|
45280
46449
|
provider: BLOCKSCOUT_PROVIDER,
|
|
@@ -45322,7 +46491,7 @@ async function getTokenHolderDistribution(args, now = Date.now) {
|
|
|
45322
46491
|
let belowFloor = false;
|
|
45323
46492
|
for (const item of items) {
|
|
45324
46493
|
const holder = item ?? {};
|
|
45325
|
-
const units =
|
|
46494
|
+
const units = parseFiniteNumber4(holder.value);
|
|
45326
46495
|
if (units === null) continue;
|
|
45327
46496
|
const usd2 = units / 10 ** decimals * price;
|
|
45328
46497
|
rows.push({ usd: usd2, is_contract: holder.address?.is_contract === true });
|
|
@@ -46390,7 +47559,7 @@ function calculateBackoffDelay2(attempt, baseDelay) {
|
|
|
46390
47559
|
const jitter = Math.random() * 500;
|
|
46391
47560
|
return exponentialDelay + jitter;
|
|
46392
47561
|
}
|
|
46393
|
-
function
|
|
47562
|
+
function sleep3(ms) {
|
|
46394
47563
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
46395
47564
|
}
|
|
46396
47565
|
async function fetchWithTimeout(url, options, timeout) {
|
|
@@ -46456,7 +47625,7 @@ async function resilientFetch(url, fetchOptions = {}, resilientOptions = {}) {
|
|
|
46456
47625
|
delayMs: Math.round(delay2),
|
|
46457
47626
|
error: lastError.message
|
|
46458
47627
|
});
|
|
46459
|
-
await
|
|
47628
|
+
await sleep3(delay2);
|
|
46460
47629
|
continue;
|
|
46461
47630
|
}
|
|
46462
47631
|
}
|
|
@@ -46652,7 +47821,7 @@ async function assertHostResolvesPublic(host) {
|
|
|
46652
47821
|
}
|
|
46653
47822
|
}
|
|
46654
47823
|
}
|
|
46655
|
-
function
|
|
47824
|
+
function buildUrl4(entry2, path, query) {
|
|
46656
47825
|
if (/^[a-z]+:\/\//i.test(path) || path.includes("@")) {
|
|
46657
47826
|
throw new FetchInputError(
|
|
46658
47827
|
`Invalid argument: path "${path}" must be a path on the host, not a full URL.`
|
|
@@ -46685,7 +47854,7 @@ async function fetchPublicApi(params) {
|
|
|
46685
47854
|
`Method "${method}" is not supported for host "${entry2.host}" (allowed: ${entry2.methods.join(", ")}).`
|
|
46686
47855
|
);
|
|
46687
47856
|
}
|
|
46688
|
-
const url =
|
|
47857
|
+
const url = buildUrl4(entry2, params.path, params.query);
|
|
46689
47858
|
await assertHostResolvesPublic(entry2.host);
|
|
46690
47859
|
const headers = { Accept: "application/json" };
|
|
46691
47860
|
let payload;
|
|
@@ -46743,7 +47912,7 @@ async function fetchPublicApi(params) {
|
|
|
46743
47912
|
}
|
|
46744
47913
|
|
|
46745
47914
|
// src/tools/fetch/tools.ts
|
|
46746
|
-
function
|
|
47915
|
+
function asRecord3(value) {
|
|
46747
47916
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
46748
47917
|
}
|
|
46749
47918
|
function getAllFetchEndpoints() {
|
|
@@ -46814,7 +47983,7 @@ Don't use when:
|
|
|
46814
47983
|
host: String(a.host ?? ""),
|
|
46815
47984
|
path: String(a.path ?? ""),
|
|
46816
47985
|
method: a.method === "POST" ? "POST" : "GET",
|
|
46817
|
-
query:
|
|
47986
|
+
query: asRecord3(a.query),
|
|
46818
47987
|
body: a.body
|
|
46819
47988
|
});
|
|
46820
47989
|
return asTextContentResult(data);
|
|
@@ -48085,7 +49254,7 @@ var PROVIDER_CATALOG = [
|
|
|
48085
49254
|
id: "rwa",
|
|
48086
49255
|
name: "RWA Perps",
|
|
48087
49256
|
prefix: "rwa_",
|
|
48088
|
-
type: "Tokenized real-world-asset perps: cross-venue funding, markets, and open interest across Hyperliquid HIP-3 builder dexes,
|
|
49257
|
+
type: "Tokenized real-world-asset perps: cross-venue funding, markets, and open interest across Hyperliquid HIP-3 builder dexes, Lighter, and Extended (keyless official APIs)",
|
|
48089
49258
|
clientStrategy: "internal",
|
|
48090
49259
|
alwaysAvailable: true,
|
|
48091
49260
|
matches: (toolName, metadata103) => matchesProvider("rwa", toolName, metadata103)
|
|
@@ -48109,11 +49278,8 @@ var PROVIDER_CATALOG = [
|
|
|
48109
49278
|
type: "Cross-venue crypto derivatives history from Hive's own archive store: funding-rate settlements across 33 CEX+DEX venues since 2023-11, basis since 2025-03, open interest and liquidations since 2025-04, and long/short ratio since 2026-03",
|
|
48110
49279
|
clientStrategy: "internal",
|
|
48111
49280
|
credentialRequirement: {
|
|
48112
|
-
label: "
|
|
48113
|
-
isConfigured: () => hasAllEnvVars(
|
|
48114
|
-
"SHARPE_ARCHIVE_SUPABASE_URL",
|
|
48115
|
-
"SHARPE_ARCHIVE_SUPABASE_SERVICE_ROLE_KEY"
|
|
48116
|
-
)
|
|
49281
|
+
label: "CLICKHOUSE_URL and CLICKHOUSE_READ_PASSWORD",
|
|
49282
|
+
isConfigured: () => hasAllEnvVars("CLICKHOUSE_URL", "CLICKHOUSE_READ_PASSWORD")
|
|
48117
49283
|
},
|
|
48118
49284
|
matches: (toolName, metadata103) => matchesProvider("archive", toolName, metadata103)
|
|
48119
49285
|
},
|
|
@@ -48126,6 +49292,15 @@ var PROVIDER_CATALOG = [
|
|
|
48126
49292
|
alwaysAvailable: true,
|
|
48127
49293
|
matches: (toolName, metadata103) => matchesProvider("polymarket", toolName, metadata103)
|
|
48128
49294
|
},
|
|
49295
|
+
{
|
|
49296
|
+
id: "kalshi",
|
|
49297
|
+
name: "Kalshi",
|
|
49298
|
+
prefix: "kalshi_",
|
|
49299
|
+
type: "Kalshi prediction markets: market and event search, market-implied odds, order books, recent trades and candlestick price history (keyless public Trade API v2)",
|
|
49300
|
+
clientStrategy: "internal",
|
|
49301
|
+
alwaysAvailable: true,
|
|
49302
|
+
matches: (toolName, metadata103) => matchesProvider("kalshi", toolName, metadata103)
|
|
49303
|
+
},
|
|
48129
49304
|
{
|
|
48130
49305
|
id: "alternative",
|
|
48131
49306
|
name: "Alternative.me",
|
|
@@ -48392,7 +49567,7 @@ var RETIRED_TOOL_REPLACEMENTS = {
|
|
|
48392
49567
|
get_community_notes: "No direct replacement; use get_categories_market_data or get_categories for category data.",
|
|
48393
49568
|
get_event_labels: "No direct replacement; use get_categories_market_data or get_categories for category data."
|
|
48394
49569
|
};
|
|
48395
|
-
var PREDICTION_REPLACEMENT = 'Prediction markets are served by first-party Polymarket tools since 1.7.0: polymarket_search_markets, polymarket_get_events, polymarket_get_market, polymarket_get_price_history, polymarket_get_orderbook, polymarket_get_wallet_positions, polymarket_get_wallet_activity. Kalshi
|
|
49570
|
+
var PREDICTION_REPLACEMENT = 'Prediction markets are served by first-party Polymarket tools since 1.7.0: polymarket_search_markets, polymarket_get_events, polymarket_get_market, polymarket_get_price_history, polymarket_get_orderbook, polymarket_get_wallet_positions, polymarket_get_wallet_activity. Kalshi markets are served by the kalshi_ tools (kalshi_search_markets, kalshi_get_market). Start with search_tools({"query": "prediction markets"}) or hive://toolsets/prediction_markets.';
|
|
48396
49571
|
var UNSERVABLE_TOOLS_DATE = "2026-09-24";
|
|
48397
49572
|
var COINGECKO_ENTERPRISE = "because it needs a CoinGecko Enterprise plan, which Hive does not have";
|
|
48398
49573
|
var COINGECKO_SUPPLY_REPLACEMENT = "Use get_coin_supply_breakdown for current circulating, total, and max supply, or get_coin_market_chart_range for a market-cap and price series.";
|
|
@@ -48424,6 +49599,7 @@ var MORALIS_WALLET_CHAINS = "Use get_wallet_balances across networks, or alchemy
|
|
|
48424
49599
|
var CCXT_PRIVATE = "because every exchange that serves it requires an authenticated exchange account";
|
|
48425
49600
|
var CCXT_BORROW_REPLACEMENT = "Use ccxt_fetch_borrow_rate_history or ccxt_fetch_borrow_rate_histories (public) and read the latest point.";
|
|
48426
49601
|
var CCXT_WRONG_DATA_DATE = "2026-10-22";
|
|
49602
|
+
var RWA_REMAINING_VENUES = "Use rwa_get_funding_rates or rwa_get_markets for the remaining RWA venues (Hyperliquid HIP-3 builder dexes, Lighter, Extended).";
|
|
48427
49603
|
var UNSERVABLE_TOOLS = {
|
|
48428
49604
|
get_coin_circulating_supply_chart: {
|
|
48429
49605
|
reason: COINGECKO_ENTERPRISE,
|
|
@@ -48801,7 +49977,18 @@ var UNSERVABLE_TOOLS = {
|
|
|
48801
49977
|
"Use alchemy_get_block_number for the latest block on an EVM network."
|
|
48802
49978
|
),
|
|
48803
49979
|
moralis_resolve_ens_domain: moralisRetired(MORALIS_ENS_NONE),
|
|
48804
|
-
moralis_resolve_address: moralisRetired(MORALIS_ENS_NONE)
|
|
49980
|
+
moralis_resolve_address: moralisRetired(MORALIS_ENS_NONE),
|
|
49981
|
+
// Removed venues (founder, 2026-10-26): the venue clients are deleted.
|
|
49982
|
+
rwa_get_ostium_markets: {
|
|
49983
|
+
reason: "because Hive no longer serves the Ostium venue",
|
|
49984
|
+
replacement: RWA_REMAINING_VENUES,
|
|
49985
|
+
date: "2026-10-26"
|
|
49986
|
+
},
|
|
49987
|
+
rwa_get_avantis_markets: {
|
|
49988
|
+
reason: "because Hive no longer serves the Avantis venue",
|
|
49989
|
+
replacement: RWA_REMAINING_VENUES,
|
|
49990
|
+
date: "2026-10-26"
|
|
49991
|
+
}
|
|
48805
49992
|
};
|
|
48806
49993
|
function isUnservableTool(toolName) {
|
|
48807
49994
|
return Object.prototype.hasOwnProperty.call(UNSERVABLE_TOOLS, toolName);
|
|
@@ -49474,6 +50661,7 @@ function getAllWrappedEndpoints() {
|
|
|
49474
50661
|
...getAllRwaEndpoints(),
|
|
49475
50662
|
...getAllArchiveEndpoints(),
|
|
49476
50663
|
...getAllPolymarketEndpoints(),
|
|
50664
|
+
...getAllKalshiEndpoints(),
|
|
49477
50665
|
...getAllSignalsEndpoints(),
|
|
49478
50666
|
...getAllBlockscoutEndpoints(),
|
|
49479
50667
|
...getAllGoldskyEndpoints(),
|
|
@@ -49552,6 +50740,7 @@ function getAllWrappedEndpoints() {
|
|
|
49552
50740
|
"rwa_",
|
|
49553
50741
|
"archive_",
|
|
49554
50742
|
"polymarket_",
|
|
50743
|
+
"kalshi_",
|
|
49555
50744
|
"signals_",
|
|
49556
50745
|
"fetch_",
|
|
49557
50746
|
"goldsky_"
|
|
@@ -49677,8 +50866,6 @@ var ToolRegistry = [
|
|
|
49677
50866
|
"rwa_get_funding_rates",
|
|
49678
50867
|
"rwa_get_markets",
|
|
49679
50868
|
"rwa_get_hyperliquid_dex_perps",
|
|
49680
|
-
"rwa_get_ostium_markets",
|
|
49681
|
-
"rwa_get_avantis_markets",
|
|
49682
50869
|
"rwa_get_lighter_markets",
|
|
49683
50870
|
"rwa_get_extended_markets",
|
|
49684
50871
|
// Hive Archive cross-venue derivatives history (since 2023-11).
|
|
@@ -50123,7 +51310,7 @@ var ToolRegistry = [
|
|
|
50123
51310
|
{
|
|
50124
51311
|
category: "Prediction Markets",
|
|
50125
51312
|
name: "get_prediction_markets_endpoints",
|
|
50126
|
-
description: "Endpoints for prediction-market intelligence on Polymarket: market and event search, market-implied odds per outcome,
|
|
51313
|
+
description: "Endpoints for prediction-market intelligence on Polymarket and Kalshi: market and event search, market-implied odds per outcome, order books, odds history and trades, and Polymarket per-wallet positions and activity. Prices are market-implied odds, not ground truth. Provider guide: polymarket_ tools use the keyless public Gamma, CLOB and Data APIs; kalshi_ tools use the keyless public Kalshi Trade API v2; Hyperliquid HIP-4 outcome markets stay under hyperliquid_get_outcome_meta and hyperliquid_get_settled_outcome.",
|
|
50127
51314
|
tools: [
|
|
50128
51315
|
"polymarket_search_markets",
|
|
50129
51316
|
"polymarket_get_events",
|
|
@@ -50131,7 +51318,13 @@ var ToolRegistry = [
|
|
|
50131
51318
|
"polymarket_get_price_history",
|
|
50132
51319
|
"polymarket_get_orderbook",
|
|
50133
51320
|
"polymarket_get_wallet_positions",
|
|
50134
|
-
"polymarket_get_wallet_activity"
|
|
51321
|
+
"polymarket_get_wallet_activity",
|
|
51322
|
+
"kalshi_search_markets",
|
|
51323
|
+
"kalshi_get_events",
|
|
51324
|
+
"kalshi_get_market",
|
|
51325
|
+
"kalshi_get_orderbook",
|
|
51326
|
+
"kalshi_get_trades",
|
|
51327
|
+
"kalshi_get_price_history"
|
|
50135
51328
|
]
|
|
50136
51329
|
}
|
|
50137
51330
|
];
|
|
@@ -50226,11 +51419,11 @@ import { fileURLToPath } from "url";
|
|
|
50226
51419
|
import Coingecko5 from "@coingecko/coingecko-typescript";
|
|
50227
51420
|
|
|
50228
51421
|
// src/tools/dynamic-tools.ts
|
|
50229
|
-
import { z as
|
|
51422
|
+
import { z as z3 } from "zod";
|
|
50230
51423
|
import { isDeepStrictEqual } from "util";
|
|
50231
51424
|
|
|
50232
51425
|
// src/tools/heroTools.ts
|
|
50233
|
-
import { z
|
|
51426
|
+
import { z } from "zod";
|
|
50234
51427
|
|
|
50235
51428
|
// src/services/tool-execution-pipeline.ts
|
|
50236
51429
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -52757,7 +53950,7 @@ var HERO_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
52757
53950
|
"check_token_safety",
|
|
52758
53951
|
"get_wallet_portfolio"
|
|
52759
53952
|
]);
|
|
52760
|
-
var RESPONSE_FORMAT =
|
|
53953
|
+
var RESPONSE_FORMAT = z.enum(["concise", "detailed", "json", "markdown"]).optional().describe(
|
|
52761
53954
|
"concise (default) returns the trimmed essentials; detailed returns the full provider payload. json and markdown, the platform-wide values every other tool advertises, are accepted and treated as concise."
|
|
52762
53955
|
);
|
|
52763
53956
|
function heroFormat(value) {
|
|
@@ -52931,7 +54124,7 @@ function formatSchemaIssues(error) {
|
|
|
52931
54124
|
}).join(" ");
|
|
52932
54125
|
}
|
|
52933
54126
|
function unknownKeysError(unknownKeys, accepted) {
|
|
52934
|
-
return
|
|
54127
|
+
return errorResult6(
|
|
52935
54128
|
`Invalid arguments: ${unknownKeys.map((key) => `"${key}" is not allowed.`).join(" ")} Accepted fields: ${accepted}.`
|
|
52936
54129
|
);
|
|
52937
54130
|
}
|
|
@@ -52946,11 +54139,11 @@ function looksLikeNameService(address) {
|
|
|
52946
54139
|
return /\.[a-z]{2,}$/i.test(address.trim());
|
|
52947
54140
|
}
|
|
52948
54141
|
function nameServiceError(address) {
|
|
52949
|
-
return
|
|
54142
|
+
return errorResult6(
|
|
52950
54143
|
`Invalid arguments: "${address}" looks like an ENS or name-service name, which isn't supported yet. Pass a 0x address (EVM) or a base58 address (Solana).`
|
|
52951
54144
|
);
|
|
52952
54145
|
}
|
|
52953
|
-
function
|
|
54146
|
+
function errorResult6(message, code = "VALIDATION_ERROR") {
|
|
52954
54147
|
return {
|
|
52955
54148
|
content: [{ type: "text", text: JSON.stringify({ error: message, code }) }],
|
|
52956
54149
|
isError: true
|
|
@@ -52984,7 +54177,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
52984
54177
|
const executeUnderlying = async (toolName, args, parentTool) => {
|
|
52985
54178
|
const lookup = resolveEndpoint(endpoints, toolName);
|
|
52986
54179
|
if (!lookup.ok) {
|
|
52987
|
-
return
|
|
54180
|
+
return errorResult6(
|
|
52988
54181
|
`Underlying endpoint '${toolName}' is not registered. Call search_tools to locate the current tool for this intent.`
|
|
52989
54182
|
);
|
|
52990
54183
|
}
|
|
@@ -53020,15 +54213,15 @@ function heroTools(endpoints, options = {}) {
|
|
|
53020
54213
|
source_receipt: sourceReceipt(result2)
|
|
53021
54214
|
});
|
|
53022
54215
|
};
|
|
53023
|
-
const tokenPriceSchema =
|
|
53024
|
-
token:
|
|
54216
|
+
const tokenPriceSchema = z.object({
|
|
54217
|
+
token: z.string().trim().min(1).max(100).optional().describe(
|
|
53025
54218
|
'CoinGecko coin id or ticker symbol, for example "bitcoin" or "BTC". Omit when passing chain + address.'
|
|
53026
54219
|
),
|
|
53027
|
-
chain:
|
|
54220
|
+
chain: z.string().trim().max(40).optional().describe(
|
|
53028
54221
|
'Chain name for the contract-address variant, for example "eth", "base", "bsc", or "solana".'
|
|
53029
54222
|
),
|
|
53030
|
-
address:
|
|
53031
|
-
vs_currency:
|
|
54223
|
+
address: z.string().trim().max(100).optional().describe("Token contract address; requires chain."),
|
|
54224
|
+
vs_currency: z.string().trim().max(20).optional().describe('Quote currency. Default "usd".'),
|
|
53032
54225
|
response_format: RESPONSE_FORMAT
|
|
53033
54226
|
});
|
|
53034
54227
|
const trimSimplePrice = (query, vsCurrency) => (payload) => {
|
|
@@ -53091,7 +54284,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53091
54284
|
}
|
|
53092
54285
|
const parsed = tokenPriceSchema.safeParse(heroArgs.args);
|
|
53093
54286
|
if (!parsed.success) {
|
|
53094
|
-
return
|
|
54287
|
+
return errorResult6(
|
|
53095
54288
|
`Invalid arguments: ${formatSchemaIssues(parsed.error)}`
|
|
53096
54289
|
);
|
|
53097
54290
|
}
|
|
@@ -53108,7 +54301,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53108
54301
|
return respond(result3, format, trimOnchainPrice(address));
|
|
53109
54302
|
}
|
|
53110
54303
|
if (!token) {
|
|
53111
|
-
return
|
|
54304
|
+
return errorResult6(
|
|
53112
54305
|
'Invalid arguments: pass token (CoinGecko id or symbol, for example "bitcoin" or "BTC"), or chain + address for a contract.'
|
|
53113
54306
|
);
|
|
53114
54307
|
}
|
|
@@ -53135,11 +54328,11 @@ function heroTools(endpoints, options = {}) {
|
|
|
53135
54328
|
);
|
|
53136
54329
|
}
|
|
53137
54330
|
};
|
|
53138
|
-
const tokenSafetySchema =
|
|
53139
|
-
address:
|
|
54331
|
+
const tokenSafetySchema = z.object({
|
|
54332
|
+
address: z.string().trim().min(1).max(100).describe(
|
|
53140
54333
|
"Required. Token contract address (0x\u2026 for EVM, base58 for Solana)."
|
|
53141
54334
|
),
|
|
53142
|
-
chain:
|
|
54335
|
+
chain: z.string().trim().max(40).optional().describe(
|
|
53143
54336
|
'Chain name or EVM chain id, for example "eth", "bsc", "8453", or "solana". Defaults to eth for 0x addresses; Solana is auto-detected.'
|
|
53144
54337
|
),
|
|
53145
54338
|
response_format: RESPONSE_FORMAT
|
|
@@ -53200,7 +54393,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53200
54393
|
}
|
|
53201
54394
|
const parsed = tokenSafetySchema.safeParse(heroArgs.args);
|
|
53202
54395
|
if (!parsed.success) {
|
|
53203
|
-
return
|
|
54396
|
+
return errorResult6(
|
|
53204
54397
|
`Invalid arguments: ${formatSchemaIssues(parsed.error)}`
|
|
53205
54398
|
);
|
|
53206
54399
|
}
|
|
@@ -53216,7 +54409,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53216
54409
|
const normalized = normalizeChain(chain);
|
|
53217
54410
|
const chainId = /^\d+$/.test(normalized) ? normalized : GOPLUS_CHAIN_IDS[normalized];
|
|
53218
54411
|
if (!chainId) {
|
|
53219
|
-
return
|
|
54412
|
+
return errorResult6(
|
|
53220
54413
|
`Invalid arguments: Unknown chain '${chain}'. Pass an EVM chain name (${Object.keys(GOPLUS_CHAIN_IDS).slice(0, 6).join(", ")}, \u2026), a numeric chain id, or "solana".`
|
|
53221
54414
|
);
|
|
53222
54415
|
}
|
|
@@ -53227,12 +54420,12 @@ function heroTools(endpoints, options = {}) {
|
|
|
53227
54420
|
return respond(result2, format, trimTokenSafety(address, normalized));
|
|
53228
54421
|
}
|
|
53229
54422
|
};
|
|
53230
|
-
const walletPortfolioSchema =
|
|
53231
|
-
address:
|
|
53232
|
-
chain:
|
|
54423
|
+
const walletPortfolioSchema = z.object({
|
|
54424
|
+
address: z.string().trim().min(1).max(100).describe("Required. Wallet address (0x\u2026 for EVM, base58 for Solana)."),
|
|
54425
|
+
chain: z.string().trim().max(40).optional().describe(
|
|
53233
54426
|
'Chain name, for example "eth", "base", "arbitrum", or "solana". Defaults to eth for 0x addresses; Solana is auto-detected.'
|
|
53234
54427
|
),
|
|
53235
|
-
include_spam:
|
|
54428
|
+
include_spam: z.boolean().optional().describe(
|
|
53236
54429
|
"Show likely spam airdrops (tokens with a URL or claim lure in the name, and on EVM chains tokens with no market price). Default false; the response says how many were hidden."
|
|
53237
54430
|
),
|
|
53238
54431
|
response_format: RESPONSE_FORMAT
|
|
@@ -53385,7 +54578,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53385
54578
|
}
|
|
53386
54579
|
const parsed = walletPortfolioSchema.safeParse(heroArgs.args);
|
|
53387
54580
|
if (!parsed.success) {
|
|
53388
|
-
return
|
|
54581
|
+
return errorResult6(
|
|
53389
54582
|
`Invalid arguments: ${formatSchemaIssues(parsed.error)}`
|
|
53390
54583
|
);
|
|
53391
54584
|
}
|
|
@@ -53424,7 +54617,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53424
54617
|
fallbackNote = "CoinGecko wallet balances failed for this call; served from Alchemy.";
|
|
53425
54618
|
}
|
|
53426
54619
|
if (!network) {
|
|
53427
|
-
return
|
|
54620
|
+
return errorResult6(
|
|
53428
54621
|
`Invalid arguments: Unknown chain '${chain}'. Pass an EVM chain name (${Object.keys(ALCHEMY_NETWORKS).slice(0, 6).join(", ")}, \u2026), a CoinGecko network id (${WALLET_BALANCE_NETWORKS.join(", ")}), or "solana".`
|
|
53429
54622
|
);
|
|
53430
54623
|
}
|
|
@@ -54268,7 +55461,7 @@ var TASK_TOOLSET_ROUTES = {
|
|
|
54268
55461
|
steps: [
|
|
54269
55462
|
step(
|
|
54270
55463
|
"funding",
|
|
54271
|
-
"Read normalized funding across all
|
|
55464
|
+
"Read normalized funding across all three RWA venues with per-venue runtime status; respect funding_mechanism, since both-sides rollover (borrow_apr_annual) is never sign-comparable with a funding transfer.",
|
|
54272
55465
|
"rwa_get_funding_rates",
|
|
54273
55466
|
"rwa_get_hyperliquid_dex_perps",
|
|
54274
55467
|
`${PROVIDER_FALLBACK} The flagship builder dexes carry the deepest RWA books when the full fan-out is unavailable.`
|
|
@@ -54310,7 +55503,7 @@ var TASK_TOOLSET_ROUTES = {
|
|
|
54310
55503
|
{
|
|
54311
55504
|
id: "rwa_builder_dex_scan",
|
|
54312
55505
|
label: "HIP-3 builder-dex scan",
|
|
54313
|
-
trigger: "Hyperliquid builder-dex, HIP-3, or pre-IPO equity perp questions (xyz,
|
|
55506
|
+
trigger: "Hyperliquid builder-dex, HIP-3, or pre-IPO equity perp questions (xyz, flx, cash, ...).",
|
|
54314
55507
|
exampleQuestion: "What is trading on Hyperliquid's builder dexes and what funding are the pre-IPO perps paying?",
|
|
54315
55508
|
requiredIdentifiers: ["builder dex name or pre-IPO underlying"],
|
|
54316
55509
|
maxMaterialCalls: 2,
|
|
@@ -55160,7 +56353,7 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
55160
56353
|
id: "rwa_perp_analysis",
|
|
55161
56354
|
kind: "product",
|
|
55162
56355
|
label: "RWA Perp Analysis",
|
|
55163
|
-
purpose: "Analyze tokenized real-world-asset perps (stocks, pre-IPO names, ETFs, indices, commodities, FX) across Hyperliquid HIP-3 builder dexes,
|
|
56356
|
+
purpose: "Analyze tokenized real-world-asset perps (stocks, pre-IPO names, ETFs, indices, commodities, FX) across Hyperliquid HIP-3 builder dexes, Lighter, and Extended: cross-venue funding, mechanism-true carry, open interest, and price-suspect checks.",
|
|
55164
56357
|
outcome: "A cross-venue RWA perp report with normalized funding (raw + annualized per venue mechanism), asset-class and delisted/price-suspect flags on every row, per-venue runtime status, and receipts.",
|
|
55165
56358
|
requiredIdentifiers: ["underlying ticker, venue, or asset class"],
|
|
55166
56359
|
optionalIdentifiers: ["builder dex name", "time range"],
|
|
@@ -55168,8 +56361,6 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
55168
56361
|
"rwa_get_funding_rates",
|
|
55169
56362
|
"rwa_get_markets",
|
|
55170
56363
|
"rwa_get_hyperliquid_dex_perps",
|
|
55171
|
-
"rwa_get_ostium_markets",
|
|
55172
|
-
"rwa_get_avantis_markets",
|
|
55173
56364
|
"rwa_get_lighter_markets",
|
|
55174
56365
|
"rwa_get_extended_markets",
|
|
55175
56366
|
"hyperliquid_get_perp_dexs",
|
|
@@ -55225,8 +56416,6 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
55225
56416
|
"carry",
|
|
55226
56417
|
"builder dex",
|
|
55227
56418
|
"hip-3",
|
|
55228
|
-
"ostium",
|
|
55229
|
-
"avantis",
|
|
55230
56419
|
"lighter",
|
|
55231
56420
|
"extended",
|
|
55232
56421
|
"price suspect"
|
|
@@ -55236,9 +56425,11 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
55236
56425
|
id: "prediction_markets",
|
|
55237
56426
|
kind: "product",
|
|
55238
56427
|
label: "Prediction Markets",
|
|
55239
|
-
purpose: "Answer prediction-market questions with market-implied odds from Polymarket: find markets and events, read order books and odds history, and inspect a trader's positions and flow.
|
|
56428
|
+
purpose: "Answer prediction-market questions with market-implied odds from Polymarket and Kalshi: find markets and events, read order books, trades and odds history, and inspect a Polymarket trader's positions and flow.",
|
|
55240
56429
|
outcome: "A sourced odds view (implied probability, liquidity, spread, trend, venue) with the caveat that prices are market-implied, not ground truth, plus per-call runtime status.",
|
|
55241
|
-
requiredIdentifiers: [
|
|
56430
|
+
requiredIdentifiers: [
|
|
56431
|
+
"market topic, Polymarket slug or condition id, or Kalshi ticker"
|
|
56432
|
+
],
|
|
55242
56433
|
optionalIdentifiers: [
|
|
55243
56434
|
"CLOB token id",
|
|
55244
56435
|
"wallet address",
|
|
@@ -55252,7 +56443,13 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
55252
56443
|
"polymarket_get_price_history",
|
|
55253
56444
|
"polymarket_get_orderbook",
|
|
55254
56445
|
"polymarket_get_wallet_positions",
|
|
55255
|
-
"polymarket_get_wallet_activity"
|
|
56446
|
+
"polymarket_get_wallet_activity",
|
|
56447
|
+
"kalshi_search_markets",
|
|
56448
|
+
"kalshi_get_events",
|
|
56449
|
+
"kalshi_get_market",
|
|
56450
|
+
"kalshi_get_orderbook",
|
|
56451
|
+
"kalshi_get_trades",
|
|
56452
|
+
"kalshi_get_price_history"
|
|
55256
56453
|
],
|
|
55257
56454
|
fallbackTools: ["hyperliquid_get_outcome_meta"],
|
|
55258
56455
|
stableFixtures: [
|
|
@@ -56567,7 +57764,7 @@ function canonicalResource(value) {
|
|
|
56567
57764
|
|
|
56568
57765
|
// src/auth/oauth/clients.ts
|
|
56569
57766
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
56570
|
-
import { z as
|
|
57767
|
+
import { z as z2 } from "zod";
|
|
56571
57768
|
import { InvalidClientMetadataError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
|
|
56572
57769
|
var OAUTH_DEPLOYMENT_CANARY_SOFTWARE_ID = "xyz.hiveintelligence/deployment-canary";
|
|
56573
57770
|
var OAUTH_DEPLOYMENT_CANARY_CLIENT_NAME = "Hive deployment OAuth canary";
|
|
@@ -56647,21 +57844,21 @@ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]
|
|
|
56647
57844
|
var EXACT_NATIVE_REDIRECT_URIS = /* @__PURE__ */ new Set([
|
|
56648
57845
|
CURSOR_DESKTOP_OAUTH_REDIRECT_URI
|
|
56649
57846
|
]);
|
|
56650
|
-
var cimdDocumentSchema =
|
|
56651
|
-
client_id:
|
|
56652
|
-
client_name:
|
|
56653
|
-
redirect_uris:
|
|
56654
|
-
token_endpoint_auth_method:
|
|
56655
|
-
grant_types:
|
|
56656
|
-
response_types:
|
|
56657
|
-
client_uri:
|
|
56658
|
-
logo_uri:
|
|
56659
|
-
scope:
|
|
56660
|
-
contacts:
|
|
56661
|
-
tos_uri:
|
|
56662
|
-
policy_uri:
|
|
56663
|
-
software_id:
|
|
56664
|
-
software_version:
|
|
57847
|
+
var cimdDocumentSchema = z2.object({
|
|
57848
|
+
client_id: z2.string().url(),
|
|
57849
|
+
client_name: z2.string().trim().min(1).max(200),
|
|
57850
|
+
redirect_uris: z2.array(z2.string().url()).min(1).max(20),
|
|
57851
|
+
token_endpoint_auth_method: z2.literal("none").optional(),
|
|
57852
|
+
grant_types: z2.array(z2.string()).max(4).optional(),
|
|
57853
|
+
response_types: z2.array(z2.string()).max(4).optional(),
|
|
57854
|
+
client_uri: z2.string().url().optional(),
|
|
57855
|
+
logo_uri: z2.string().url().optional(),
|
|
57856
|
+
scope: z2.string().max(1e3).optional(),
|
|
57857
|
+
contacts: z2.array(z2.string().max(320)).max(10).optional(),
|
|
57858
|
+
tos_uri: z2.string().url().optional(),
|
|
57859
|
+
policy_uri: z2.string().url().optional(),
|
|
57860
|
+
software_id: z2.string().max(200).optional(),
|
|
57861
|
+
software_version: z2.string().max(100).optional()
|
|
56665
57862
|
}).strip();
|
|
56666
57863
|
function registeredClientTtlSeconds(client2, configuredTtlSeconds) {
|
|
56667
57864
|
return client2.software_id === OAUTH_DEPLOYMENT_CANARY_SOFTWARE_ID ? Math.min(
|
|
@@ -57083,41 +58280,41 @@ var HIVE_PROMPTS = [
|
|
|
57083
58280
|
}
|
|
57084
58281
|
];
|
|
57085
58282
|
|
|
57086
|
-
// src/services/
|
|
57087
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
57088
|
-
var _unkey = null;
|
|
58283
|
+
// src/services/api-key-service.ts
|
|
58284
|
+
import { createHash as createHash6, randomInt, randomUUID as randomUUID4 } from "crypto";
|
|
57089
58285
|
function getAuthBackendMode() {
|
|
57090
58286
|
const mode = (getEnvVar("HIVE_AUTH_BACKEND") ?? "auto").toLowerCase();
|
|
57091
|
-
if (mode === "
|
|
57092
|
-
|
|
57093
|
-
}
|
|
58287
|
+
if (mode === "hosted" || mode === "unkey") return "hosted";
|
|
58288
|
+
if (mode === "static") return "static";
|
|
57094
58289
|
return "auto";
|
|
57095
58290
|
}
|
|
57096
|
-
function
|
|
57097
|
-
return getAuthBackendMode() === "
|
|
58291
|
+
function isHostedKeysRequired() {
|
|
58292
|
+
return getAuthBackendMode() === "hosted";
|
|
57098
58293
|
}
|
|
57099
|
-
function
|
|
57100
|
-
return
|
|
58294
|
+
function isHostedKeysConfigured() {
|
|
58295
|
+
return getAuthBackendMode() !== "static" && isSupabaseConfigured();
|
|
57101
58296
|
}
|
|
57102
|
-
function
|
|
57103
|
-
return
|
|
58297
|
+
function isLegacyUnkeyConfigured() {
|
|
58298
|
+
return hasEnvVars("UNKEY_ROOT_KEY");
|
|
57104
58299
|
}
|
|
57105
|
-
|
|
57106
|
-
|
|
57107
|
-
|
|
57108
|
-
|
|
57109
|
-
|
|
57110
|
-
|
|
57111
|
-
|
|
57112
|
-
const { Unkey: UnkeyClient } = await import("@unkey/api");
|
|
57113
|
-
_unkey ??= new UnkeyClient({
|
|
57114
|
-
rootKey: getRequiredEnvVar("UNKEY_ROOT_KEY")
|
|
57115
|
-
});
|
|
57116
|
-
}
|
|
58300
|
+
var _unkey = null;
|
|
58301
|
+
function legacyUnkey() {
|
|
58302
|
+
_unkey ??= createFetchClient({
|
|
58303
|
+
baseURL: "https://api.unkey.com/v2",
|
|
58304
|
+
timeout: 5e3,
|
|
58305
|
+
headers: { Authorization: `Bearer ${getEnvVar("UNKEY_ROOT_KEY")}` }
|
|
58306
|
+
});
|
|
57117
58307
|
return _unkey;
|
|
57118
58308
|
}
|
|
57119
|
-
|
|
57120
|
-
|
|
58309
|
+
var KEY_PREFIX = "hive_live_";
|
|
58310
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
58311
|
+
var START_LENGTH = KEY_PREFIX.length + 4;
|
|
58312
|
+
var BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
58313
|
+
function randomBase58(length) {
|
|
58314
|
+
return Array.from({ length }, () => BASE58[randomInt(BASE58.length)]).join("");
|
|
58315
|
+
}
|
|
58316
|
+
function hashKey(key) {
|
|
58317
|
+
return createHash6("sha256").update(key).digest("hex");
|
|
57121
58318
|
}
|
|
57122
58319
|
function normalizeAnalyticsTags(tags) {
|
|
57123
58320
|
const normalized = /* @__PURE__ */ new Set();
|
|
@@ -57130,6 +58327,24 @@ function normalizeAnalyticsTags(tags) {
|
|
|
57130
58327
|
}
|
|
57131
58328
|
return [...normalized];
|
|
57132
58329
|
}
|
|
58330
|
+
function withoutStalePlan(meta2) {
|
|
58331
|
+
if (!meta2) return void 0;
|
|
58332
|
+
const { plan: _stalePlan, ...rest } = meta2;
|
|
58333
|
+
return rest;
|
|
58334
|
+
}
|
|
58335
|
+
var VERIFY_CACHE_TTL_MS = 3e4;
|
|
58336
|
+
var VERIFY_CACHE_MAX = 1e4;
|
|
58337
|
+
var verifyCache = /* @__PURE__ */ new Map();
|
|
58338
|
+
function cacheVerified(keyHash, result2) {
|
|
58339
|
+
if (verifyCache.size >= VERIFY_CACHE_MAX) {
|
|
58340
|
+
const oldest = verifyCache.keys().next().value;
|
|
58341
|
+
if (oldest !== void 0) verifyCache.delete(oldest);
|
|
58342
|
+
}
|
|
58343
|
+
verifyCache.set(keyHash, {
|
|
58344
|
+
result: result2,
|
|
58345
|
+
expiresAt: Date.now() + VERIFY_CACHE_TTL_MS
|
|
58346
|
+
});
|
|
58347
|
+
}
|
|
57133
58348
|
async function getPlan(planId) {
|
|
57134
58349
|
const supabase = getSupabaseAdmin();
|
|
57135
58350
|
const { data, error } = await supabase.from("plans").select("*").eq("id", planId).single();
|
|
@@ -57138,32 +58353,10 @@ async function getPlan(planId) {
|
|
|
57138
58353
|
}
|
|
57139
58354
|
return data;
|
|
57140
58355
|
}
|
|
57141
|
-
var KEYS_PAGE_LIMIT = 100;
|
|
57142
|
-
var KEYS_MAX_PAGES = 100;
|
|
57143
|
-
async function listAllUserKeysRaw(userId) {
|
|
57144
|
-
const apiId = getApiId();
|
|
57145
|
-
const all = [];
|
|
57146
|
-
let cursor;
|
|
57147
|
-
for (let page = 0; page < KEYS_MAX_PAGES; page += 1) {
|
|
57148
|
-
const unkey = await getUnkey();
|
|
57149
|
-
const result2 = await unkey.apis.listKeys({
|
|
57150
|
-
apiId,
|
|
57151
|
-
externalId: userId,
|
|
57152
|
-
limit: KEYS_PAGE_LIMIT,
|
|
57153
|
-
revalidateKeysCache: true,
|
|
57154
|
-
...cursor ? { cursor } : {}
|
|
57155
|
-
});
|
|
57156
|
-
const keys = Array.isArray(result2.result.data) ? result2.result.data : [];
|
|
57157
|
-
all.push(...keys);
|
|
57158
|
-
const pagination = result2.result.pagination;
|
|
57159
|
-
if (!pagination?.hasMore || !pagination.cursor) break;
|
|
57160
|
-
cursor = pagination.cursor;
|
|
57161
|
-
}
|
|
57162
|
-
return all;
|
|
57163
|
-
}
|
|
57164
58356
|
async function countActiveUserKeys(userId) {
|
|
57165
|
-
const
|
|
57166
|
-
|
|
58357
|
+
const { count, error } = await getSupabaseAdmin().from("api_keys").select("id", { count: "exact", head: true }).eq("user_id", userId).eq("enabled", true);
|
|
58358
|
+
if (error) throw new Error(`Counting API keys failed: ${error.message}`);
|
|
58359
|
+
return count ?? 0;
|
|
57167
58360
|
}
|
|
57168
58361
|
var KEY_CREATION_LEASE_TTL_SECONDS = 120;
|
|
57169
58362
|
var KEY_CREATION_LEASE_WAIT_MS = 1e4;
|
|
@@ -57219,82 +58412,114 @@ async function createApiKey(opts) {
|
|
|
57219
58412
|
subject_signing_secret: subjectSigningSecret
|
|
57220
58413
|
} : {}
|
|
57221
58414
|
};
|
|
57222
|
-
const
|
|
57223
|
-
const
|
|
57224
|
-
|
|
57225
|
-
|
|
58415
|
+
const key = KEY_PREFIX + randomBase58(33);
|
|
58416
|
+
const keyId = `key_${randomBase58(16)}`;
|
|
58417
|
+
const { error } = await getSupabaseAdmin().from("api_keys").insert({
|
|
58418
|
+
id: keyId,
|
|
58419
|
+
user_id: opts.userId,
|
|
57226
58420
|
name: opts.name,
|
|
57227
|
-
|
|
57228
|
-
|
|
57229
|
-
meta: meta2
|
|
57230
|
-
enabled: true
|
|
58421
|
+
start: key.slice(0, START_LENGTH),
|
|
58422
|
+
key_hash: hashKey(key),
|
|
58423
|
+
meta: meta2
|
|
57231
58424
|
});
|
|
57232
|
-
if (
|
|
57233
|
-
secureLogger.error("
|
|
57234
|
-
|
|
58425
|
+
if (error) {
|
|
58426
|
+
secureLogger.error("API key insert failed", error, {
|
|
58427
|
+
userId: opts.userId
|
|
57235
58428
|
});
|
|
57236
58429
|
throw new Error("Failed to create API key");
|
|
57237
58430
|
}
|
|
57238
|
-
return {
|
|
57239
|
-
keyId: result2.data.keyId,
|
|
57240
|
-
key: result2.data.key,
|
|
57241
|
-
subjectSigningSecret
|
|
57242
|
-
};
|
|
58431
|
+
return { keyId, key, subjectSigningSecret };
|
|
57243
58432
|
} finally {
|
|
57244
58433
|
await releaseKeyCreationLease(opts.userId, leaseToken);
|
|
57245
58434
|
}
|
|
57246
58435
|
}
|
|
57247
|
-
async function
|
|
58436
|
+
async function verifyLegacyUnkeyKey(rawKey, keyHash, analyticsTags) {
|
|
57248
58437
|
const tags = normalizeAnalyticsTags(analyticsTags);
|
|
57249
|
-
const
|
|
57250
|
-
const result2 = await unkey.keys.verifyKey({
|
|
58438
|
+
const { data: body } = await legacyUnkey().post("/keys.verifyKey", {
|
|
57251
58439
|
key: rawKey,
|
|
57252
58440
|
...tags.length > 0 ? { tags } : {}
|
|
57253
58441
|
});
|
|
57254
|
-
|
|
57255
|
-
|
|
57256
|
-
|
|
57257
|
-
|
|
57258
|
-
|
|
58442
|
+
const d = body?.data;
|
|
58443
|
+
if (!d) throw new Error("Unkey verifyKey answered without a result");
|
|
58444
|
+
if (!d.valid) return { valid: false, code: d.code ?? "UNKNOWN" };
|
|
58445
|
+
const userId = d.identity?.externalId;
|
|
58446
|
+
if (!d.keyId || !userId || !UUID_PATTERN.test(userId)) {
|
|
58447
|
+
return { valid: false, code: "NOT_FOUND" };
|
|
58448
|
+
}
|
|
58449
|
+
const { data: enabled, error } = await getSupabaseAdmin().rpc(
|
|
58450
|
+
"migrate_unkey_api_key",
|
|
58451
|
+
{
|
|
58452
|
+
p_id: d.keyId,
|
|
58453
|
+
p_user_id: userId,
|
|
58454
|
+
p_key_hash: keyHash,
|
|
58455
|
+
p_name: d.name ?? "Unnamed",
|
|
58456
|
+
p_start: rawKey.slice(0, START_LENGTH),
|
|
58457
|
+
p_meta: withoutStalePlan(d.meta) ?? {}
|
|
58458
|
+
}
|
|
58459
|
+
);
|
|
58460
|
+
if (error) {
|
|
58461
|
+
throw new Error(`Recording a legacy API key failed: ${error.message}`);
|
|
58462
|
+
}
|
|
58463
|
+
if (enabled !== true) {
|
|
58464
|
+
return { valid: false, code: "DISABLED", keyId: d.keyId };
|
|
57259
58465
|
}
|
|
57260
|
-
const d = result2.data;
|
|
57261
58466
|
return {
|
|
57262
|
-
valid:
|
|
57263
|
-
code:
|
|
58467
|
+
valid: true,
|
|
58468
|
+
code: "VALID",
|
|
57264
58469
|
keyId: d.keyId,
|
|
57265
|
-
userId
|
|
58470
|
+
userId,
|
|
57266
58471
|
name: d.name,
|
|
57267
|
-
|
|
57268
|
-
meta: d.meta ?? void 0
|
|
58472
|
+
meta: withoutStalePlan(d.meta)
|
|
57269
58473
|
};
|
|
57270
58474
|
}
|
|
58475
|
+
async function verifyApiKey(rawKey, analyticsTags = []) {
|
|
58476
|
+
const keyHash = hashKey(rawKey);
|
|
58477
|
+
const cached = verifyCache.get(keyHash);
|
|
58478
|
+
if (cached && cached.expiresAt > Date.now()) return cached.result;
|
|
58479
|
+
const { data: row, error } = await getSupabaseAdmin().from("api_keys").select("id, user_id, name, meta, enabled").eq("key_hash", keyHash).maybeSingle();
|
|
58480
|
+
if (error) throw new Error(`API key lookup failed: ${error.message}`);
|
|
58481
|
+
let result2;
|
|
58482
|
+
if (row) {
|
|
58483
|
+
result2 = row.enabled ? {
|
|
58484
|
+
valid: true,
|
|
58485
|
+
code: "VALID",
|
|
58486
|
+
keyId: row.id,
|
|
58487
|
+
userId: row.user_id,
|
|
58488
|
+
name: row.name,
|
|
58489
|
+
meta: row.meta ?? void 0
|
|
58490
|
+
} : { valid: false, code: "DISABLED", keyId: row.id };
|
|
58491
|
+
} else if (isLegacyUnkeyConfigured()) {
|
|
58492
|
+
result2 = await verifyLegacyUnkeyKey(rawKey, keyHash, analyticsTags);
|
|
58493
|
+
} else {
|
|
58494
|
+
result2 = { valid: false, code: "NOT_FOUND" };
|
|
58495
|
+
}
|
|
58496
|
+
if (result2.valid) cacheVerified(keyHash, result2);
|
|
58497
|
+
return result2;
|
|
58498
|
+
}
|
|
57271
58499
|
async function listKeys(userId) {
|
|
57272
|
-
const
|
|
57273
|
-
if (keys
|
|
57274
|
-
return
|
|
57275
|
-
keyId: k.
|
|
58500
|
+
const { data, error } = await getSupabaseAdmin().from("api_keys").select("id, name, start, enabled, meta, created_at").eq("user_id", userId).order("created_at", { ascending: true });
|
|
58501
|
+
if (error) throw new Error(`Listing API keys failed: ${error.message}`);
|
|
58502
|
+
return (data ?? []).map((k) => ({
|
|
58503
|
+
keyId: k.id,
|
|
57276
58504
|
name: k.name ?? "Unnamed",
|
|
57277
58505
|
prefix: k.start ?? "",
|
|
57278
|
-
createdAt:
|
|
58506
|
+
createdAt: new Date(k.created_at).toISOString(),
|
|
57279
58507
|
enabled: k.enabled ?? true,
|
|
57280
|
-
|
|
57281
|
-
|
|
57282
|
-
meta: withoutStalePlan(
|
|
57283
|
-
sanitizeKeyMeta(k.meta ?? void 0)
|
|
57284
|
-
)
|
|
58508
|
+
externalId: userId,
|
|
58509
|
+
meta: withoutStalePlan(sanitizeKeyMeta(k.meta ?? void 0))
|
|
57285
58510
|
}));
|
|
57286
58511
|
}
|
|
57287
|
-
function withoutStalePlan(meta2) {
|
|
57288
|
-
if (!meta2) return void 0;
|
|
57289
|
-
const { plan: _stalePlan, ...rest } = meta2;
|
|
57290
|
-
return rest;
|
|
57291
|
-
}
|
|
57292
58512
|
async function revokeKey(keyId) {
|
|
57293
|
-
const
|
|
57294
|
-
|
|
57295
|
-
|
|
57296
|
-
|
|
57297
|
-
|
|
58513
|
+
const { data, error } = await getSupabaseAdmin().from("api_keys").update({ enabled: false }).eq("id", keyId).select("key_hash, source").maybeSingle();
|
|
58514
|
+
if (error) throw new Error(`Revoking API key failed: ${error.message}`);
|
|
58515
|
+
if (data?.key_hash) verifyCache.delete(data.key_hash);
|
|
58516
|
+
if (data?.source === "unkey" && isLegacyUnkeyConfigured()) {
|
|
58517
|
+
await legacyUnkey().post("/keys.updateKey", { keyId, enabled: false }).catch(
|
|
58518
|
+
(err) => secureLogger.error("Unkey disable failed; the key stays revoked in Hive", err, {
|
|
58519
|
+
keyId
|
|
58520
|
+
})
|
|
58521
|
+
);
|
|
58522
|
+
}
|
|
57298
58523
|
}
|
|
57299
58524
|
|
|
57300
58525
|
// src/health/mcpProtocol.ts
|
|
@@ -57304,28 +58529,28 @@ function getStaticApiKeys() {
|
|
|
57304
58529
|
function getHttpMcpAuthReadiness() {
|
|
57305
58530
|
const backend = getAuthBackendMode();
|
|
57306
58531
|
const staticKeyCount = getStaticApiKeys().length;
|
|
57307
|
-
if (
|
|
57308
|
-
if (!
|
|
58532
|
+
if (isHostedKeysRequired()) {
|
|
58533
|
+
if (!isHostedKeysConfigured()) {
|
|
57309
58534
|
return {
|
|
57310
58535
|
status: "missing_key",
|
|
57311
58536
|
backend,
|
|
57312
58537
|
configured: false,
|
|
57313
|
-
detail: "
|
|
58538
|
+
detail: "Hosted API key authentication is selected but SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY is missing."
|
|
57314
58539
|
};
|
|
57315
58540
|
}
|
|
57316
58541
|
return {
|
|
57317
58542
|
status: "ok",
|
|
57318
58543
|
backend,
|
|
57319
58544
|
configured: true,
|
|
57320
|
-
detail: "
|
|
58545
|
+
detail: "Hosted API key authentication is configured for HTTP MCP."
|
|
57321
58546
|
};
|
|
57322
58547
|
}
|
|
57323
|
-
if (
|
|
58548
|
+
if (isHostedKeysConfigured()) {
|
|
57324
58549
|
return {
|
|
57325
58550
|
status: "ok",
|
|
57326
58551
|
backend,
|
|
57327
58552
|
configured: true,
|
|
57328
|
-
detail: "
|
|
58553
|
+
detail: "Hosted API key authentication is configured for HTTP MCP."
|
|
57329
58554
|
};
|
|
57330
58555
|
}
|
|
57331
58556
|
if (staticKeyCount > 0) {
|
|
@@ -57340,7 +58565,7 @@ function getHttpMcpAuthReadiness() {
|
|
|
57340
58565
|
status: "missing_key",
|
|
57341
58566
|
backend,
|
|
57342
58567
|
configured: false,
|
|
57343
|
-
detail: "HTTP MCP authentication is not configured. Set
|
|
58568
|
+
detail: "HTTP MCP authentication is not configured. Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY for hosted keys, or HIVE_AUTH_BACKEND=static with HIVE_API_KEYS."
|
|
57344
58569
|
};
|
|
57345
58570
|
}
|
|
57346
58571
|
async function timedCheck(name, fn) {
|
|
@@ -57725,7 +58950,7 @@ async function runMcpProtocolHealth(options = {}) {
|
|
|
57725
58950
|
}
|
|
57726
58951
|
|
|
57727
58952
|
// src/health/oauthLifecycle.ts
|
|
57728
|
-
import { createHash as
|
|
58953
|
+
import { createHash as createHash8, randomBytes as randomBytes3 } from "crypto";
|
|
57729
58954
|
|
|
57730
58955
|
// src/auth/oauth/provider.ts
|
|
57731
58956
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
@@ -58041,8 +59266,8 @@ var HiveOAuthProvider = class {
|
|
|
58041
59266
|
await this.storage.revokeFamily(record.familyId);
|
|
58042
59267
|
throw error;
|
|
58043
59268
|
}
|
|
58044
|
-
const
|
|
58045
|
-
if (
|
|
59269
|
+
const invalid3 = record.kind !== "refresh" || record.clientId !== client2.client_id || record.expiresAt <= Math.floor(Date.now() / 1e3) || !resourceMatches || !hasOnlySupportedOAuthScopes(requestedScopes) || requestedScopes.some((scope) => !record.scopes.includes(scope));
|
|
59270
|
+
if (invalid3) {
|
|
58046
59271
|
await this.storage.revokeFamily(record.familyId);
|
|
58047
59272
|
throw new InvalidGrantError(
|
|
58048
59273
|
"Refresh token is not valid for this client, resource, or scope set."
|
|
@@ -58101,10 +59326,10 @@ function isHiveOAuthAccessToken(token) {
|
|
|
58101
59326
|
}
|
|
58102
59327
|
|
|
58103
59328
|
// src/auth/oauth/store.ts
|
|
58104
|
-
import { createHash as
|
|
59329
|
+
import { createHash as createHash7 } from "crypto";
|
|
58105
59330
|
var REVOKED_FAMILY_TTL_SECONDS = 7776e3;
|
|
58106
59331
|
function digest(value) {
|
|
58107
|
-
return
|
|
59332
|
+
return createHash7("sha256").update(value).digest("hex");
|
|
58108
59333
|
}
|
|
58109
59334
|
function parseJson(raw) {
|
|
58110
59335
|
if (!raw) return void 0;
|
|
@@ -58513,7 +59738,7 @@ var OAUTH_LIFECYCLE_CHECK_NAMES = [
|
|
|
58513
59738
|
"token_revocation",
|
|
58514
59739
|
"connected_app_cleanup"
|
|
58515
59740
|
];
|
|
58516
|
-
var
|
|
59741
|
+
var UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
58517
59742
|
function elapsed(started) {
|
|
58518
59743
|
return Math.max(0, Date.now() - started);
|
|
58519
59744
|
}
|
|
@@ -58556,7 +59781,7 @@ async function operationRejects(operation) {
|
|
|
58556
59781
|
}
|
|
58557
59782
|
async function issueCanaryFamily(args) {
|
|
58558
59783
|
const verifier = randomBytes3(48).toString("base64url");
|
|
58559
|
-
const challenge =
|
|
59784
|
+
const challenge = createHash8("sha256").update(verifier).digest("base64url");
|
|
58560
59785
|
let consentLocation;
|
|
58561
59786
|
const response = {
|
|
58562
59787
|
redirect(status, location) {
|
|
@@ -58676,7 +59901,7 @@ async function runOAuthLifecycleHealth(overrides = {}) {
|
|
|
58676
59901
|
}
|
|
58677
59902
|
const userStarted = Date.now();
|
|
58678
59903
|
const canaryUserId = dependencies.canaryUserId;
|
|
58679
|
-
if (!canaryUserId || !
|
|
59904
|
+
if (!canaryUserId || !UUID_PATTERN2.test(canaryUserId)) {
|
|
58680
59905
|
checks.push(
|
|
58681
59906
|
check(
|
|
58682
59907
|
"canary_identity",
|
|
@@ -58868,12 +60093,12 @@ async function runOAuthLifecycleHealth(overrides = {}) {
|
|
|
58868
60093
|
import Coingecko4 from "@coingecko/coingecko-typescript";
|
|
58869
60094
|
|
|
58870
60095
|
// src/tools/goplus/goplusClient.ts
|
|
58871
|
-
import { createHash as
|
|
60096
|
+
import { createHash as createHash9 } from "crypto";
|
|
58872
60097
|
function parsePositiveInteger(value, fallback) {
|
|
58873
60098
|
const parsed = Number.parseInt(value || "", 10);
|
|
58874
60099
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
58875
60100
|
}
|
|
58876
|
-
function
|
|
60101
|
+
function sleep4(ms) {
|
|
58877
60102
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
58878
60103
|
}
|
|
58879
60104
|
function chainIdPathSegment(value) {
|
|
@@ -58962,7 +60187,7 @@ var GoPlusClient = class {
|
|
|
58962
60187
|
const time = Math.floor(Date.now() / 1e3);
|
|
58963
60188
|
const timeStr = time.toString();
|
|
58964
60189
|
const signInput = this.appKey + timeStr + this.appSecret;
|
|
58965
|
-
const sign =
|
|
60190
|
+
const sign = createHash9("sha1").update(signInput).digest("hex");
|
|
58966
60191
|
secureLogger.info("GoPlus: requesting access token", {
|
|
58967
60192
|
appKeyLength: this.appKey.length,
|
|
58968
60193
|
appSecretLength: this.appSecret.length,
|
|
@@ -59179,7 +60404,7 @@ var GoPlusClient = class {
|
|
|
59179
60404
|
maxAttempts: this.rateLimitRetryAttempts + 1,
|
|
59180
60405
|
backoffMs
|
|
59181
60406
|
});
|
|
59182
|
-
await
|
|
60407
|
+
await sleep4(backoffMs);
|
|
59183
60408
|
}
|
|
59184
60409
|
if (result2 && typeof result2.code === "number" && result2.code !== 1 && result2.code !== 2) {
|
|
59185
60410
|
const isRateLimit = result2.code === 4029;
|
|
@@ -59630,6 +60855,9 @@ var CCXTClient = class {
|
|
|
59630
60855
|
if (this.exchanges.has(cacheKey2)) {
|
|
59631
60856
|
return this.exchanges.get(cacheKey2);
|
|
59632
60857
|
}
|
|
60858
|
+
if (REMOVED_CCXT_EXCHANGES.has(exchangeId.toLowerCase())) {
|
|
60859
|
+
throw new Error(removedVenueMessage(exchangeId));
|
|
60860
|
+
}
|
|
59633
60861
|
if (!ccxt[exchangeId]) {
|
|
59634
60862
|
throw new Error(
|
|
59635
60863
|
`Exchange '${exchangeId}' is not supported. Use one of: binance, coinbase, kraken, kucoin, etc.`
|
|
@@ -60490,6 +61718,7 @@ var PROVIDER_RESOURCE_PATTERNS = [
|
|
|
60490
61718
|
["rwa", /^rwa(?:[.-]|$)/],
|
|
60491
61719
|
["archive", /^archive(?:[.-]|$)/],
|
|
60492
61720
|
["polymarket", /^polymarket(?:[.-]|$)/],
|
|
61721
|
+
["kalshi", /^kalshi(?:[.-]|$)/],
|
|
60493
61722
|
["alternative", /^signals-fear-greed$/],
|
|
60494
61723
|
["blockchain", /^signals-onchain/],
|
|
60495
61724
|
["blockscout", /^blockscout(?:[.-]|$)/],
|
|
@@ -60516,6 +61745,7 @@ function classifyProvider(endpoint) {
|
|
|
60516
61745
|
if (toolName.startsWith("rwa_")) return "rwa";
|
|
60517
61746
|
if (toolName.startsWith("archive_")) return "archive";
|
|
60518
61747
|
if (toolName.startsWith("polymarket_")) return "polymarket";
|
|
61748
|
+
if (toolName.startsWith("kalshi_")) return "kalshi";
|
|
60519
61749
|
if (toolName === "signals_get_fear_greed") return "alternative";
|
|
60520
61750
|
if (toolName.startsWith("signals_get_onchain")) return "blockchain";
|
|
60521
61751
|
if (toolName.startsWith("fetch_")) return "fetch";
|
|
@@ -60544,8 +61774,8 @@ var QUICK_PROBE_ARGS = {
|
|
|
60544
61774
|
args: { network: "eth-mainnet" }
|
|
60545
61775
|
},
|
|
60546
61776
|
hyperliquid: { toolName: "hyperliquid_get_perp_dexs", args: {} },
|
|
60547
|
-
// Single-request keyless venue read (official
|
|
60548
|
-
rwa: { toolName: "
|
|
61777
|
+
// Single-request keyless venue read (official Extended markets endpoint).
|
|
61778
|
+
rwa: { toolName: "rwa_get_extended_markets", args: { limit: 5 } },
|
|
60549
61779
|
// Cheap coverage probe (index-only extremes, 10-min in-process cache).
|
|
60550
61780
|
// Unset archive credentials classify as missing-key skip, not failure.
|
|
60551
61781
|
archive: { toolName: "archive_get_coverage", args: {} },
|
|
@@ -61709,6 +62939,7 @@ function getClientForProvider(provider, clients) {
|
|
|
61709
62939
|
case "defillama":
|
|
61710
62940
|
case "archive":
|
|
61711
62941
|
case "polymarket":
|
|
62942
|
+
case "kalshi":
|
|
61712
62943
|
case "alternative":
|
|
61713
62944
|
case "blockchain":
|
|
61714
62945
|
case "goldsky":
|
|
@@ -61979,9 +63210,22 @@ function projectArgsToToolSchema(args, schema) {
|
|
|
61979
63210
|
Object.entries(args).filter(([key]) => key in properties)
|
|
61980
63211
|
);
|
|
61981
63212
|
}
|
|
63213
|
+
var UNPROBED_PROVIDERS = {
|
|
63214
|
+
kalshi: "Not probed: the Kalshi developer agreement (section 3.5) forbids probing its API for availability or performance."
|
|
63215
|
+
};
|
|
61982
63216
|
async function runEndpointProbe(endpoint, provider, clients, args, timeoutMs = PROBE_TIMEOUT) {
|
|
61983
63217
|
const start = Date.now();
|
|
61984
63218
|
const toolName = endpoint.tool.name;
|
|
63219
|
+
const unprobedReason = UNPROBED_PROVIDERS[provider];
|
|
63220
|
+
if (unprobedReason) {
|
|
63221
|
+
return {
|
|
63222
|
+
name: toolName,
|
|
63223
|
+
status: "skipped",
|
|
63224
|
+
duration: 0,
|
|
63225
|
+
error: unprobedReason,
|
|
63226
|
+
runtimeStatus: "ok"
|
|
63227
|
+
};
|
|
63228
|
+
}
|
|
61985
63229
|
const isWriteCapable = endpoint.metadata.operation === "write" || endpoint.tool.annotations?.readOnlyHint === false;
|
|
61986
63230
|
if (isWriteCapable && process.env.HIVE_E2E_ALLOW_WRITE_TOOLS !== "true") {
|
|
61987
63231
|
return {
|
|
@@ -62347,8 +63591,8 @@ var TASK_CANARY_PROBES = [
|
|
|
62347
63591
|
},
|
|
62348
63592
|
{
|
|
62349
63593
|
toolsetId: "rwa_perp_analysis",
|
|
62350
|
-
toolName: "
|
|
62351
|
-
description: "
|
|
63594
|
+
toolName: "rwa_get_extended_markets",
|
|
63595
|
+
description: "Extended RWA markets check (single-request keyless venue read).",
|
|
62352
63596
|
args: { limit: 5 }
|
|
62353
63597
|
},
|
|
62354
63598
|
{
|
|
@@ -64086,8 +65330,6 @@ var TOOLSET_INTENT_HINTS = {
|
|
|
64086
65330
|
"rwa perp",
|
|
64087
65331
|
"pre-ipo",
|
|
64088
65332
|
"pre ipo",
|
|
64089
|
-
"ostium",
|
|
64090
|
-
"avantis",
|
|
64091
65333
|
"rollover"
|
|
64092
65334
|
],
|
|
64093
65335
|
wallet_investigation: [
|
|
@@ -64402,7 +65644,7 @@ var TASK_RESULT_VALIDATION_OUTPUT_SCHEMA = {
|
|
|
64402
65644
|
warnings: { type: "array", items: { type: "string" } }
|
|
64403
65645
|
}
|
|
64404
65646
|
};
|
|
64405
|
-
function
|
|
65647
|
+
function errorResult7(message) {
|
|
64406
65648
|
return {
|
|
64407
65649
|
content: [
|
|
64408
65650
|
{
|
|
@@ -64829,32 +66071,32 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
64829
66071
|
const taskToolsetCatalogEntries = TASK_TOOLSETS.map(
|
|
64830
66072
|
(toolset) => toolsetCatalogEntry(toolset, providerByToolName)
|
|
64831
66073
|
);
|
|
64832
|
-
const searchToolsSchema =
|
|
64833
|
-
query:
|
|
66074
|
+
const searchToolsSchema = z3.object({
|
|
66075
|
+
query: z3.string().max(512).optional().describe(
|
|
64834
66076
|
"Search text matched against tool name, title, category, provider, and summary."
|
|
64835
66077
|
),
|
|
64836
|
-
provider:
|
|
66078
|
+
provider: z3.string().max(128).optional().describe(
|
|
64837
66079
|
"Optional provider filter such as CoinGecko, Tenderly, Helius, GoPlus, CCXT, Alchemy, DeFiLlama, Hyperliquid, or Open Data Fetch."
|
|
64838
66080
|
),
|
|
64839
|
-
category:
|
|
64840
|
-
limit:
|
|
64841
|
-
cursor:
|
|
64842
|
-
toolset_id:
|
|
66081
|
+
category: z3.string().max(128).optional().describe("Optional category filter, for example Portfolio & Wallet."),
|
|
66082
|
+
limit: z3.number().int().min(1).max(MAX_SEARCH_LIMIT).optional().describe(`Maximum tools to return. Default ${DEFAULT_SEARCH_LIMIT}.`),
|
|
66083
|
+
cursor: z3.string().optional().describe("Pagination cursor from the previous response."),
|
|
66084
|
+
toolset_id: z3.string().max(100).optional().describe(
|
|
64843
66085
|
"Optional exact canonical task toolset id. Use this after an initial compact result to inspect one workflow."
|
|
64844
66086
|
),
|
|
64845
|
-
route_id:
|
|
66087
|
+
route_id: z3.string().max(100).optional().describe(
|
|
64846
66088
|
"Optional exact task-shaped route id. Use the matched route's ordered calls and conditional fallbacks instead of choosing from the coverage catalog."
|
|
64847
66089
|
),
|
|
64848
|
-
evidence_phase:
|
|
66090
|
+
evidence_phase: z3.string().max(100).optional().describe(
|
|
64849
66091
|
`Optional exact workflow evidence phase. Supported values: ${CANONICAL_EVIDENCE_PHASES.join(", ")}.`
|
|
64850
66092
|
),
|
|
64851
|
-
detail:
|
|
66093
|
+
detail: z3.enum(["compact", "standard", "full"]).optional().describe(
|
|
64852
66094
|
"Toolset detail level. Defaults to compact. Use full only when the task output schema is needed."
|
|
64853
66095
|
),
|
|
64854
|
-
toolset_limit:
|
|
66096
|
+
toolset_limit: z3.number().int().min(1).max(MAX_TOOLSET_LIMIT).optional().describe(
|
|
64855
66097
|
`Maximum matching task toolsets to return. Default ${DEFAULT_TOOLSET_LIMIT}, max ${MAX_TOOLSET_LIMIT}.`
|
|
64856
66098
|
),
|
|
64857
|
-
toolset_cursor:
|
|
66099
|
+
toolset_cursor: z3.string().optional().describe("Pagination cursor for task toolset matches.")
|
|
64858
66100
|
});
|
|
64859
66101
|
const searchToolsTool = {
|
|
64860
66102
|
metadata: {
|
|
@@ -64988,9 +66230,9 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
64988
66230
|
});
|
|
64989
66231
|
}
|
|
64990
66232
|
};
|
|
64991
|
-
const getEndpointSchema =
|
|
64992
|
-
endpoint:
|
|
64993
|
-
endpoint_name:
|
|
66233
|
+
const getEndpointSchema = z3.object({
|
|
66234
|
+
endpoint: z3.string().optional().describe("The name of the endpoint to get the schema for."),
|
|
66235
|
+
endpoint_name: z3.string().optional().describe(
|
|
64994
66236
|
"Alias for 'endpoint', accepted for symmetry with invoke_api_endpoint. Provide one of the two."
|
|
64995
66237
|
)
|
|
64996
66238
|
});
|
|
@@ -65027,12 +66269,12 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65027
66269
|
},
|
|
65028
66270
|
handler: async (_client, args) => {
|
|
65029
66271
|
const missingEndpoint = "Invalid arguments: no endpoint provided. Pass an 'endpoint' (or 'endpoint_name') parameter with one tool name as a string.";
|
|
65030
|
-
if (!args) return
|
|
66272
|
+
if (!args) return errorResult7(missingEndpoint);
|
|
65031
66273
|
const parsedArgs = getEndpointSchema.safeParse(args);
|
|
65032
|
-
if (!parsedArgs.success) return
|
|
66274
|
+
if (!parsedArgs.success) return errorResult7(missingEndpoint);
|
|
65033
66275
|
const parsedEndpointArgs = parsedArgs.data;
|
|
65034
66276
|
const requestedEndpoint = parsedEndpointArgs.endpoint ?? parsedEndpointArgs.endpoint_name ?? firstStringArg(args, ENDPOINT_NAME_ALIASES);
|
|
65035
|
-
if (!requestedEndpoint) return
|
|
66277
|
+
if (!requestedEndpoint) return errorResult7(missingEndpoint);
|
|
65036
66278
|
const lookup = resolveEndpoint(endpoints, requestedEndpoint);
|
|
65037
66279
|
const metaEndpoint = lookup.ok ? void 0 : [
|
|
65038
66280
|
searchToolsTool,
|
|
@@ -65068,24 +66310,24 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65068
66310
|
});
|
|
65069
66311
|
}
|
|
65070
66312
|
};
|
|
65071
|
-
const invokeEndpointSchema =
|
|
65072
|
-
endpoint_name:
|
|
65073
|
-
endpoint:
|
|
66313
|
+
const invokeEndpointSchema = z3.object({
|
|
66314
|
+
endpoint_name: z3.string().optional().describe("The name of the endpoint to invoke."),
|
|
66315
|
+
endpoint: z3.string().optional().describe(
|
|
65074
66316
|
"Alias for 'endpoint_name', accepted by both root invokers. Provide one of the two."
|
|
65075
66317
|
),
|
|
65076
|
-
args:
|
|
66318
|
+
args: z3.record(z3.string(), z3.any()).optional().describe(
|
|
65077
66319
|
"The arguments to pass to the endpoint. This must match the schema returned by the `get_api_endpoint_schema` tool. You may also pass arguments as flat top-level keys alongside endpoint_name."
|
|
65078
66320
|
)
|
|
65079
66321
|
}).passthrough();
|
|
65080
66322
|
const invokeWithPolicy = async (invoker, client2, args) => {
|
|
65081
66323
|
const { success, data, error } = invokeEndpointSchema.safeParse(args);
|
|
65082
66324
|
if (!success) {
|
|
65083
|
-
return
|
|
66325
|
+
return errorResult7(
|
|
65084
66326
|
`Invalid arguments: ${JSON.stringify(error?.format())}`
|
|
65085
66327
|
);
|
|
65086
66328
|
}
|
|
65087
66329
|
const invokeArgs = normalizeInvokeArgs(data);
|
|
65088
|
-
if (!invokeArgs.ok) return
|
|
66330
|
+
if (!invokeArgs.ok) return errorResult7(invokeArgs.message);
|
|
65089
66331
|
const lookup = resolveEndpoint(endpoints, invokeArgs.resolvedToolName);
|
|
65090
66332
|
if (!lookup.ok) {
|
|
65091
66333
|
return createToolNotFoundResult(
|
|
@@ -65093,7 +66335,7 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65093
66335
|
).mcpResult;
|
|
65094
66336
|
}
|
|
65095
66337
|
if (!endpointAllowedForInvoker(invoker, lookup.endpoint)) {
|
|
65096
|
-
return
|
|
66338
|
+
return errorResult7(invokerPolicyMessage(invoker, lookup.endpoint));
|
|
65097
66339
|
}
|
|
65098
66340
|
const executionContext = {
|
|
65099
66341
|
transport: "dynamic",
|
|
@@ -65144,9 +66386,9 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65144
66386
|
},
|
|
65145
66387
|
handler: async (client2, args) => invokeWithPolicy(STATEFUL_WRITE_ENDPOINT_INVOKER, client2, args)
|
|
65146
66388
|
};
|
|
65147
|
-
const validateTaskResultSchema =
|
|
65148
|
-
task_toolset_id:
|
|
65149
|
-
result:
|
|
66389
|
+
const validateTaskResultSchema = z3.object({
|
|
66390
|
+
task_toolset_id: z3.string().describe("Canonical task toolset id returned by search_tools."),
|
|
66391
|
+
result: z3.record(z3.string(), z3.any()).describe(
|
|
65150
66392
|
"The proposed task result envelope. Each receipt.calls entry must be copied exactly from a material call's _hive object."
|
|
65151
66393
|
)
|
|
65152
66394
|
});
|
|
@@ -65167,7 +66409,7 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65167
66409
|
handler: async (_client, args) => {
|
|
65168
66410
|
const parsed = validateTaskResultSchema.safeParse(args);
|
|
65169
66411
|
if (!parsed.success) {
|
|
65170
|
-
return
|
|
66412
|
+
return errorResult7(
|
|
65171
66413
|
`Invalid task result validation request: ${JSON.stringify(parsed.error.format())}`
|
|
65172
66414
|
);
|
|
65173
66415
|
}
|
|
@@ -65175,7 +66417,7 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65175
66417
|
(entry2) => entry2.id === parsed.data.task_toolset_id
|
|
65176
66418
|
);
|
|
65177
66419
|
if (!toolset) {
|
|
65178
|
-
return
|
|
66420
|
+
return errorResult7(
|
|
65179
66421
|
`Unknown task toolset '${parsed.data.task_toolset_id}'. Use search_tools to select a canonical id.`
|
|
65180
66422
|
);
|
|
65181
66423
|
}
|
|
@@ -65438,10 +66680,10 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65438
66680
|
}
|
|
65439
66681
|
};
|
|
65440
66682
|
const categoryTools = ToolRegistry.map((category, categoryIndex) => {
|
|
65441
|
-
const categorySchema =
|
|
65442
|
-
search:
|
|
65443
|
-
limit:
|
|
65444
|
-
cursor:
|
|
66683
|
+
const categorySchema = z3.object({
|
|
66684
|
+
search: z3.string().optional().describe("Ignored; retained for 1.2.0 compatibility."),
|
|
66685
|
+
limit: z3.number().int().optional().describe("Ignored; retained for 1.2.0 compatibility."),
|
|
66686
|
+
cursor: z3.string().optional().describe("Ignored; retained for 1.2.0 compatibility.")
|
|
65445
66687
|
});
|
|
65446
66688
|
const categoryEndpointName = category.name;
|
|
65447
66689
|
const categoryPath = CategoryEndpoints[categoryIndex] ?? "";
|
|
@@ -65520,7 +66762,7 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65520
66762
|
}
|
|
65521
66763
|
|
|
65522
66764
|
// src/utils/jsonSchemaToZod.ts
|
|
65523
|
-
import { z as
|
|
66765
|
+
import { z as z4 } from "zod";
|
|
65524
66766
|
var TOP_LEVEL_COMBINATORS = /* @__PURE__ */ new Set([
|
|
65525
66767
|
"allOf",
|
|
65526
66768
|
"anyOf",
|
|
@@ -65602,10 +66844,10 @@ function zodPath(field) {
|
|
|
65602
66844
|
});
|
|
65603
66845
|
}
|
|
65604
66846
|
function jsonSchemaToZodInputSchema(schema) {
|
|
65605
|
-
const base =
|
|
66847
|
+
const base = z4.fromJSONSchema(
|
|
65606
66848
|
zodBaseSchema(schema)
|
|
65607
66849
|
);
|
|
65608
|
-
if (!(base instanceof
|
|
66850
|
+
if (!(base instanceof z4.ZodObject)) {
|
|
65609
66851
|
throw new Error("Tool input schema must compile to a Zod object");
|
|
65610
66852
|
}
|
|
65611
66853
|
const properties = schema.properties ?? {};
|
|
@@ -65652,34 +66894,34 @@ function jsonSchemaToZodShape(schema) {
|
|
|
65652
66894
|
return shape;
|
|
65653
66895
|
}
|
|
65654
66896
|
function jsonSchemaToZodObject(schema, options = {}) {
|
|
65655
|
-
const objectSchema =
|
|
66897
|
+
const objectSchema = z4.object(jsonSchemaToZodShape(schema));
|
|
65656
66898
|
return options.passthrough ? objectSchema.passthrough() : objectSchema;
|
|
65657
66899
|
}
|
|
65658
66900
|
function buildZodType(prop) {
|
|
65659
66901
|
const type = prop.type;
|
|
65660
66902
|
const enumValues = prop.enum;
|
|
65661
66903
|
if (enumValues && enumValues.length > 0) {
|
|
65662
|
-
return
|
|
66904
|
+
return z4.enum(enumValues);
|
|
65663
66905
|
}
|
|
65664
66906
|
switch (type) {
|
|
65665
66907
|
case "string":
|
|
65666
|
-
return
|
|
66908
|
+
return z4.string();
|
|
65667
66909
|
case "number":
|
|
65668
66910
|
case "integer":
|
|
65669
|
-
return
|
|
66911
|
+
return z4.number();
|
|
65670
66912
|
case "boolean":
|
|
65671
|
-
return
|
|
66913
|
+
return z4.boolean();
|
|
65672
66914
|
case "array":
|
|
65673
|
-
return
|
|
66915
|
+
return z4.array(z4.unknown());
|
|
65674
66916
|
case "object":
|
|
65675
|
-
return
|
|
66917
|
+
return z4.record(z4.string(), z4.unknown());
|
|
65676
66918
|
default:
|
|
65677
|
-
return
|
|
66919
|
+
return z4.unknown();
|
|
65678
66920
|
}
|
|
65679
66921
|
}
|
|
65680
66922
|
|
|
65681
66923
|
// src/mcpServer.ts
|
|
65682
|
-
|
|
66924
|
+
loadEnvFile();
|
|
65683
66925
|
var ROOT_META_TOOL_COUNT = getRootVisibleToolCount();
|
|
65684
66926
|
var ROOT_VISIBLE_TOOL_ORDER = [
|
|
65685
66927
|
"get_token_price",
|
|
@@ -66425,12 +67667,6 @@ var HiveMCPServer = class {
|
|
|
66425
67667
|
"/api/health/toolsets/quick",
|
|
66426
67668
|
"/api/health/toolsets/history"
|
|
66427
67669
|
],
|
|
66428
|
-
unkeyAnalyticsTags: [
|
|
66429
|
-
"service=hive",
|
|
66430
|
-
"transport=rest",
|
|
66431
|
-
"route=<route>",
|
|
66432
|
-
"endpoint=<tool_name>"
|
|
66433
|
-
],
|
|
66434
67670
|
mcpToolCallLogFields: [
|
|
66435
67671
|
"tool",
|
|
66436
67672
|
"success",
|
|
@@ -66607,14 +67843,6 @@ function createHiveMcpServer(scope, options) {
|
|
|
66607
67843
|
}
|
|
66608
67844
|
|
|
66609
67845
|
export {
|
|
66610
|
-
getEnvVar,
|
|
66611
|
-
getCsvEnv,
|
|
66612
|
-
getNodeEnv,
|
|
66613
|
-
isProduction,
|
|
66614
|
-
getPort,
|
|
66615
|
-
getRateLimitMax,
|
|
66616
|
-
getCredentialedRateLimitMax,
|
|
66617
|
-
getCorsAllowedOrigins,
|
|
66618
67846
|
isSupabaseConfigured,
|
|
66619
67847
|
getSupabaseAdmin,
|
|
66620
67848
|
getUserPlanId,
|
|
@@ -66635,8 +67863,6 @@ export {
|
|
|
66635
67863
|
runWithRequestPrincipal,
|
|
66636
67864
|
principalKeyFromRaw,
|
|
66637
67865
|
submitFeedback,
|
|
66638
|
-
HyperliquidAPI,
|
|
66639
|
-
collectRwaRows,
|
|
66640
67866
|
isFreeWriteEndpoint,
|
|
66641
67867
|
trackServerEvent,
|
|
66642
67868
|
trackCliCommand,
|
|
@@ -66679,8 +67905,8 @@ export {
|
|
|
66679
67905
|
OAUTH_CLIENT_REDIRECT_PROFILES,
|
|
66680
67906
|
MAX_DCR_CLIENT_METADATA_BYTES,
|
|
66681
67907
|
buildAuthCompatibility,
|
|
66682
|
-
|
|
66683
|
-
|
|
67908
|
+
isHostedKeysRequired,
|
|
67909
|
+
isHostedKeysConfigured,
|
|
66684
67910
|
createApiKey,
|
|
66685
67911
|
verifyApiKey,
|
|
66686
67912
|
listKeys,
|