hive-intelligence 1.14.0 → 1.15.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/{chunk-SMVHHINA.js → chunk-762ZUJ4Q.js} +1 -1
- package/build/{chunk-C4BFUIEB.js → chunk-GNZGLVFE.js} +1692 -490
- package/build/cli.js +5 -5
- package/build/{doctor-FPB5TEBU.js → doctor-25WPHR4J.js} +1 -1
- package/build/{mcpServer-RGUKLIP2.js → mcpServer-O4KXRE62.js} +1 -1
- package/build/monitor-worker.js +3 -154
- package/build/release.json +4 -4
- package/build/{serve-46DQ37YQ.js → serve-V2KPPJCD.js} +1 -1
- package/build/server.js +6 -2
- package/build/stdio.js +1 -1
- package/package.json +1 -1
|
@@ -8841,8 +8841,8 @@ Don't use when:
|
|
|
8841
8841
|
annotations: VOLATILE_READ_ANNOTATIONS
|
|
8842
8842
|
};
|
|
8843
8843
|
var handler91 = async (client2, args) => {
|
|
8844
|
-
const
|
|
8845
|
-
if (
|
|
8844
|
+
const invalid3 = walletWindowError(args);
|
|
8845
|
+
if (invalid3) return invalid3;
|
|
8846
8846
|
const network = encodeURIComponent(String(args?.network ?? "").trim());
|
|
8847
8847
|
const address = encodeURIComponent(String(args?.address ?? "").trim());
|
|
8848
8848
|
const query = {
|
|
@@ -8926,8 +8926,8 @@ Don't use when:
|
|
|
8926
8926
|
annotations: VOLATILE_READ_ANNOTATIONS
|
|
8927
8927
|
};
|
|
8928
8928
|
var handler92 = async (client2, args) => {
|
|
8929
|
-
const
|
|
8930
|
-
if (
|
|
8929
|
+
const invalid3 = walletWindowError(args);
|
|
8930
|
+
if (invalid3) return invalid3;
|
|
8931
8931
|
const network = encodeURIComponent(String(args?.network ?? "").trim());
|
|
8932
8932
|
const address = encodeURIComponent(String(args?.address ?? "").trim());
|
|
8933
8933
|
const query = {
|
|
@@ -9017,7 +9017,7 @@ Use when:
|
|
|
9017
9017
|
|
|
9018
9018
|
Don't use when:
|
|
9019
9019
|
- 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,
|
|
9020
|
+
- 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
9021
|
- You need the underlying token contracts for an asset (use coingecko_get_rwa_asset with tokens=true)
|
|
9022
9022
|
- You want a regular crypto coin ID (use coingecko_get_coins_list)
|
|
9023
9023
|
|
|
@@ -9115,7 +9115,7 @@ Use when:
|
|
|
9115
9115
|
- You want to size an issuer such as Ondo, Backed, or Paxos by the assets it wraps
|
|
9116
9116
|
|
|
9117
9117
|
Don't use when:
|
|
9118
|
-
- You need RWA perpetual markets or funding rates on Hyperliquid,
|
|
9118
|
+
- You need RWA perpetual markets or funding rates on Hyperliquid, Lighter, or Extended (use rwa_get_markets or rwa_get_funding_rates)
|
|
9119
9119
|
- You need per-exchange or per-pair trades for one asset (use coingecko_get_rwa_tickers)
|
|
9120
9120
|
- You need price history rather than a snapshot (use coingecko_get_rwa_market_chart)
|
|
9121
9121
|
- You need the token contract addresses behind an asset (use coingecko_get_rwa_asset with tokens=true)
|
|
@@ -9351,7 +9351,7 @@ Use when:
|
|
|
9351
9351
|
|
|
9352
9352
|
Don't use when:
|
|
9353
9353
|
- 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,
|
|
9354
|
+
- 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
9355
|
- You need historical prices (use coingecko_get_rwa_market_chart)
|
|
9356
9356
|
- You want tickers for a regular crypto coin (use coingecko_get_coin_tickers)
|
|
9357
9357
|
|
|
@@ -9696,7 +9696,7 @@ var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
|
9696
9696
|
"GOPLUS_API_KEY"
|
|
9697
9697
|
]);
|
|
9698
9698
|
function isSensitiveEnvironmentKey(key) {
|
|
9699
|
-
return /(?:api[_-]?(?:key|id)|key|secret|password|token|auth|credential|private|bearer|redis.*url|
|
|
9699
|
+
return /(?:api[_-]?(?:key|id)|key|secret|password|token|auth|credential|private|bearer|redis.*url|clickhouse_url)/i.test(
|
|
9700
9700
|
key
|
|
9701
9701
|
);
|
|
9702
9702
|
}
|
|
@@ -20416,8 +20416,8 @@ Error Handling:
|
|
|
20416
20416
|
},
|
|
20417
20417
|
async execute(args) {
|
|
20418
20418
|
const MAX_SAFE_LIMIT = 50;
|
|
20419
|
-
const
|
|
20420
|
-
const limit = Math.min(args?.limit ||
|
|
20419
|
+
const DEFAULT_LIMIT5 = 30;
|
|
20420
|
+
const limit = Math.min(args?.limit || DEFAULT_LIMIT5, MAX_SAFE_LIMIT);
|
|
20421
20421
|
const offset = args?.offset || 0;
|
|
20422
20422
|
try {
|
|
20423
20423
|
const data = await apiClient.getProtocols();
|
|
@@ -21525,8 +21525,8 @@ Error Handling:
|
|
|
21525
21525
|
},
|
|
21526
21526
|
async execute(args) {
|
|
21527
21527
|
const MAX_SAFE_LIMIT = 100;
|
|
21528
|
-
const
|
|
21529
|
-
const limit = Math.min(args?.limit ||
|
|
21528
|
+
const DEFAULT_LIMIT5 = 50;
|
|
21529
|
+
const limit = Math.min(args?.limit || DEFAULT_LIMIT5, MAX_SAFE_LIMIT);
|
|
21530
21530
|
const offset = args?.offset || 0;
|
|
21531
21531
|
const minTvl = args?.minTvl || 0;
|
|
21532
21532
|
try {
|
|
@@ -23642,8 +23642,8 @@ Error Handling:
|
|
|
23642
23642
|
},
|
|
23643
23643
|
async execute(args) {
|
|
23644
23644
|
const MAX_SAFE_LIMIT = 100;
|
|
23645
|
-
const
|
|
23646
|
-
const limit = Math.min(args.limit ||
|
|
23645
|
+
const DEFAULT_LIMIT5 = 50;
|
|
23646
|
+
const limit = Math.min(args.limit || DEFAULT_LIMIT5, MAX_SAFE_LIMIT);
|
|
23647
23647
|
const offset = args.offset || 0;
|
|
23648
23648
|
const minFees = args.minFees || 0;
|
|
23649
23649
|
try {
|
|
@@ -38745,6 +38745,24 @@ function getAllAlchemyEndpoints() {
|
|
|
38745
38745
|
return endpoints;
|
|
38746
38746
|
}
|
|
38747
38747
|
|
|
38748
|
+
// src/config/removedVenues.ts
|
|
38749
|
+
var REMOVED_CCXT_EXCHANGES = /* @__PURE__ */ new Set([
|
|
38750
|
+
"bitmex",
|
|
38751
|
+
"coinex",
|
|
38752
|
+
"coinbaseinternational",
|
|
38753
|
+
"dydx",
|
|
38754
|
+
"woofipro"
|
|
38755
|
+
]);
|
|
38756
|
+
var REMOVED_HYPERLIQUID_DEXES = /* @__PURE__ */ new Set([
|
|
38757
|
+
"km",
|
|
38758
|
+
"mkts",
|
|
38759
|
+
"para",
|
|
38760
|
+
"io"
|
|
38761
|
+
]);
|
|
38762
|
+
function removedVenueMessage(venue) {
|
|
38763
|
+
return `Invalid arguments: Hive does not serve the venue "${venue}" (removed 2026-10-26). Pick another venue.`;
|
|
38764
|
+
}
|
|
38765
|
+
|
|
38748
38766
|
// src/tools/hyperliquid/api/hyperliquid.ts
|
|
38749
38767
|
import axios3 from "axios";
|
|
38750
38768
|
var HYPERLIQUID_API_URL = "https://api.hyperliquid.xyz";
|
|
@@ -38775,6 +38793,13 @@ var HyperliquidAPI = class {
|
|
|
38775
38793
|
"User-Agent": "hive-mcp-server/1.0.0"
|
|
38776
38794
|
}
|
|
38777
38795
|
});
|
|
38796
|
+
this.client.interceptors.request.use((config2) => {
|
|
38797
|
+
const dex = config2.data?.dex;
|
|
38798
|
+
if (typeof dex === "string" && REMOVED_HYPERLIQUID_DEXES.has(dex)) {
|
|
38799
|
+
throw new Error(removedVenueMessage(`hyperliquid:${dex}`));
|
|
38800
|
+
}
|
|
38801
|
+
return config2;
|
|
38802
|
+
});
|
|
38778
38803
|
this.client.interceptors.response.use(
|
|
38779
38804
|
(response) => response,
|
|
38780
38805
|
(error) => {
|
|
@@ -38840,7 +38865,9 @@ var HyperliquidAPI = class {
|
|
|
38840
38865
|
name: String(d.name ?? ""),
|
|
38841
38866
|
fullName: d.full_name ?? d.fullName ?? null,
|
|
38842
38867
|
deployer: d.deployer ?? null
|
|
38843
|
-
})).filter(
|
|
38868
|
+
})).filter(
|
|
38869
|
+
(d) => d.name.length > 0 && !REMOVED_HYPERLIQUID_DEXES.has(d.name)
|
|
38870
|
+
);
|
|
38844
38871
|
return { builderDexCount: builderDexs.length, builderDexs };
|
|
38845
38872
|
}
|
|
38846
38873
|
/**
|
|
@@ -39722,7 +39749,7 @@ var HYPERLIQUID_INFO_DESCRIPTORS = [
|
|
|
39722
39749
|
"You need outcome identifiers for hyperliquid_get_settled_outcome"
|
|
39723
39750
|
],
|
|
39724
39751
|
dontUseWhen: [
|
|
39725
|
-
"You need Polymarket markets (use the polymarket_ tools) or Kalshi markets (
|
|
39752
|
+
"You need Polymarket markets (use the polymarket_ tools) or Kalshi markets (use the kalshi_ tools)"
|
|
39726
39753
|
],
|
|
39727
39754
|
params: []
|
|
39728
39755
|
},
|
|
@@ -40482,8 +40509,6 @@ function nextIntervalBoundaryIso(intervalHours, now = Date.now()) {
|
|
|
40482
40509
|
return new Date(Math.ceil(now / ms) * ms).toISOString();
|
|
40483
40510
|
}
|
|
40484
40511
|
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
40512
|
function annualizeFundingRate(ratePerInterval, intervalHours) {
|
|
40488
40513
|
if (ratePerInterval == null || !Number.isFinite(ratePerInterval)) {
|
|
40489
40514
|
return null;
|
|
@@ -40491,16 +40516,6 @@ function annualizeFundingRate(ratePerInterval, intervalHours) {
|
|
|
40491
40516
|
if (!Number.isFinite(intervalHours) || intervalHours <= 0) return null;
|
|
40492
40517
|
return ratePerInterval * (HOURS_PER_YEAR / intervalHours);
|
|
40493
40518
|
}
|
|
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
40519
|
var ETFS = /* @__PURE__ */ new Set([
|
|
40505
40520
|
"SPY",
|
|
40506
40521
|
"QQQ",
|
|
@@ -40749,117 +40764,6 @@ function stampPriceSuspect(rows) {
|
|
|
40749
40764
|
return rows;
|
|
40750
40765
|
}
|
|
40751
40766
|
|
|
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
40767
|
// src/tools/rwa/venues/extended.ts
|
|
40864
40768
|
var EXTENDED_BASE = "https://api.starknet.extended.exchange/api/v1";
|
|
40865
40769
|
var EXTENDED_INTERVAL_HOURS = 1;
|
|
@@ -41177,89 +41081,9 @@ async function fetchLighterRows(fetchJson = defaultRwaFetchJson) {
|
|
|
41177
41081
|
return rows;
|
|
41178
41082
|
}
|
|
41179
41083
|
|
|
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
41084
|
// src/tools/rwa/venues/index.ts
|
|
41259
41085
|
var RWA_VENUE_IDS = [
|
|
41260
41086
|
"hyperliquid_dexes",
|
|
41261
|
-
"ostium",
|
|
41262
|
-
"avantis",
|
|
41263
41087
|
"lighter",
|
|
41264
41088
|
"extended"
|
|
41265
41089
|
];
|
|
@@ -41281,10 +41105,6 @@ async function fetchRwaVenueRows(venue, deps) {
|
|
|
41281
41105
|
note: noteParts.length > 0 ? noteParts.join(" | ") : void 0
|
|
41282
41106
|
};
|
|
41283
41107
|
}
|
|
41284
|
-
case "ostium":
|
|
41285
|
-
return { rows: await fetchOstiumRows(deps.fetchJson) };
|
|
41286
|
-
case "avantis":
|
|
41287
|
-
return { rows: await fetchAvantisRows(deps.fetchJson) };
|
|
41288
41108
|
case "lighter":
|
|
41289
41109
|
return { rows: await fetchLighterRows(deps.fetchJson) };
|
|
41290
41110
|
case "extended":
|
|
@@ -41421,7 +41241,7 @@ var FILTER_PARAM_PROPERTIES = {
|
|
|
41421
41241
|
},
|
|
41422
41242
|
venue: {
|
|
41423
41243
|
type: "string",
|
|
41424
|
-
description: 'Venue filter: "
|
|
41244
|
+
description: 'Venue filter: "lighter", "extended", "hyperliquid" (all builder dexes), or a specific builder dex like "hyperliquid:xyz".'
|
|
41425
41245
|
},
|
|
41426
41246
|
asset_class: {
|
|
41427
41247
|
type: "string",
|
|
@@ -41543,7 +41363,7 @@ function getAllRwaEndpoints() {
|
|
|
41543
41363
|
},
|
|
41544
41364
|
tool: {
|
|
41545
41365
|
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
|
|
41366
|
+
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
41367
|
|
|
41548
41368
|
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
41369
|
|
|
@@ -41551,7 +41371,7 @@ ${ROW_CONTRACT}
|
|
|
41551
41371
|
|
|
41552
41372
|
Args:
|
|
41553
41373
|
- underlying (string, optional): filter to one canonical underlying (e.g. "TSLA").
|
|
41554
|
-
- venue (string, optional): filter to one venue ("
|
|
41374
|
+
- venue (string, optional): filter to one venue ("lighter", "extended", "hyperliquid", or "hyperliquid:xyz").
|
|
41555
41375
|
- asset_class (string, optional): filter to one asset class (equity, pre_ipo, etf, index, commodity, fx, crypto).
|
|
41556
41376
|
- limit (integer, optional): max rows (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}), deepest open interest first.
|
|
41557
41377
|
- offset (integer, optional): rows to skip; pass next_offset to page.
|
|
@@ -41616,7 +41436,7 @@ Don't use when:
|
|
|
41616
41436
|
},
|
|
41617
41437
|
tool: {
|
|
41618
41438
|
name: "rwa_get_markets",
|
|
41619
|
-
description: `Get the tokenized real-world-asset perp UNIVERSE across all
|
|
41439
|
+
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
41440
|
|
|
41621
41441
|
Venues are queried with per-venue error isolation (per-venue runtime_status; partial results always flow).
|
|
41622
41442
|
|
|
@@ -41691,7 +41511,7 @@ Don't use when:
|
|
|
41691
41511
|
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
41512
|
useWhen: [
|
|
41693
41513
|
"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,
|
|
41514
|
+
"You are checking builder-set funding on a specific dex (xyz, flx, cash, ...)"
|
|
41695
41515
|
],
|
|
41696
41516
|
dontUseWhen: [
|
|
41697
41517
|
"You need main-dex Hyperliquid crypto perps (use hyperliquid_get_meta / hyperliquid_get_perp_volume_leaderboard)",
|
|
@@ -41699,38 +41519,6 @@ Don't use when:
|
|
|
41699
41519
|
],
|
|
41700
41520
|
deps
|
|
41701
41521
|
}),
|
|
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
41522
|
venueRawEndpoint({
|
|
41735
41523
|
toolName: "rwa_get_lighter_markets",
|
|
41736
41524
|
resource: "rwa-lighter-markets",
|
|
@@ -41767,55 +41555,51 @@ Don't use when:
|
|
|
41767
41555
|
}
|
|
41768
41556
|
|
|
41769
41557
|
// src/tools/archive/client.ts
|
|
41770
|
-
|
|
41771
|
-
var
|
|
41772
|
-
var
|
|
41773
|
-
var
|
|
41774
|
-
var
|
|
41558
|
+
var ARCHIVE_URL_ENV = "CLICKHOUSE_URL";
|
|
41559
|
+
var ARCHIVE_PASSWORD_ENV = "CLICKHOUSE_READ_PASSWORD";
|
|
41560
|
+
var ARCHIVE_USER = "hive_read";
|
|
41561
|
+
var REQUEST_TIMEOUT_MS = 2e4;
|
|
41562
|
+
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
41563
|
function isArchiveConfigured() {
|
|
41776
|
-
return hasEnvVars(ARCHIVE_URL_ENV,
|
|
41564
|
+
return hasEnvVars(ARCHIVE_URL_ENV, ARCHIVE_PASSWORD_ENV);
|
|
41777
41565
|
}
|
|
41778
41566
|
function getArchiveClient() {
|
|
41779
41567
|
if (!isArchiveConfigured()) return null;
|
|
41780
|
-
|
|
41781
|
-
|
|
41782
|
-
|
|
41783
|
-
|
|
41784
|
-
|
|
41785
|
-
);
|
|
41786
|
-
|
|
41787
|
-
|
|
41568
|
+
const base = getEnvVar(ARCHIVE_URL_ENV);
|
|
41569
|
+
const auth = Buffer.from(
|
|
41570
|
+
`${ARCHIVE_USER}:${getEnvVar(ARCHIVE_PASSWORD_ENV)}`
|
|
41571
|
+
).toString("base64");
|
|
41572
|
+
return async (sql, params = {}) => {
|
|
41573
|
+
const url = new URL(base);
|
|
41574
|
+
url.searchParams.set("date_time_output_format", "iso");
|
|
41575
|
+
for (const [name, value] of Object.entries(params)) {
|
|
41576
|
+
url.searchParams.set(`param_${name}`, String(value));
|
|
41577
|
+
}
|
|
41578
|
+
const response = await fetch(url, {
|
|
41579
|
+
method: "POST",
|
|
41580
|
+
headers: { Authorization: `Basic ${auth}` },
|
|
41581
|
+
body: `${sql} FORMAT JSONEachRow`,
|
|
41582
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
41583
|
+
});
|
|
41584
|
+
const body = await response.text();
|
|
41585
|
+
if (!response.ok) {
|
|
41586
|
+
throw new Error(`ClickHouse ${response.status}: ${body.slice(0, 500)}`);
|
|
41587
|
+
}
|
|
41588
|
+
return body.split("\n").filter((line) => line !== "").map((line) => JSON.parse(line));
|
|
41589
|
+
};
|
|
41788
41590
|
}
|
|
41789
41591
|
|
|
41790
41592
|
// src/tools/archive/api.ts
|
|
41791
41593
|
var DEFAULT_LIMIT2 = 100;
|
|
41792
41594
|
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;
|
|
41595
|
+
var VENUE_LIST_MAX = 64;
|
|
41803
41596
|
var COVERAGE_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
41804
41597
|
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
41598
|
function clampLimit3(value) {
|
|
41812
41599
|
const parsed = typeof value === "number" ? Math.floor(value) : NaN;
|
|
41813
41600
|
if (!Number.isFinite(parsed)) return DEFAULT_LIMIT2;
|
|
41814
41601
|
return Math.min(Math.max(parsed, 1), MAX_LIMIT2);
|
|
41815
41602
|
}
|
|
41816
|
-
function escapeLikeLiteral(value) {
|
|
41817
|
-
return value.replace(/[\\%_]/g, (match) => `\\${match}`);
|
|
41818
|
-
}
|
|
41819
41603
|
var ArchiveInputError = class extends Error {
|
|
41820
41604
|
};
|
|
41821
41605
|
function normalizeBaseCoin(value) {
|
|
@@ -41858,96 +41642,129 @@ function parseHistoryArgs(args) {
|
|
|
41858
41642
|
};
|
|
41859
41643
|
}
|
|
41860
41644
|
function archiveQueryError(context, error) {
|
|
41861
|
-
const detail = error.message
|
|
41862
|
-
|
|
41863
|
-
if (error.code === "57014" || /timeout/i.test(detail)) {
|
|
41645
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
41646
|
+
if (/Code: (158|159|160|396)\b|TimeoutError|aborted|timeout/i.test(detail) || error instanceof Error && error.name === "TimeoutError") {
|
|
41864
41647
|
return new Error(
|
|
41865
|
-
`Hive Archive query timed out while ${context}
|
|
41648
|
+
`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
41649
|
);
|
|
41867
41650
|
}
|
|
41868
|
-
if (/
|
|
41651
|
+
if (/ClickHouse (401|403)\b|Code: 516\b|AUTHENTICATION_FAILED/.test(detail)) {
|
|
41869
41652
|
return new Error(
|
|
41870
|
-
`Hive Archive rejected the configured credentials while ${context}
|
|
41653
|
+
`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
41654
|
);
|
|
41872
41655
|
}
|
|
41873
41656
|
return new Error(
|
|
41874
|
-
`Hive Archive query failed while ${context}
|
|
41657
|
+
`Hive Archive query failed while ${context}: ${detail}. Retry once; if it persists the archive is degraded \u2014 use live derivatives tools instead.`
|
|
41875
41658
|
);
|
|
41876
41659
|
}
|
|
41877
41660
|
var HISTORY_TABLES = {
|
|
41878
41661
|
funding: {
|
|
41879
|
-
table: "
|
|
41880
|
-
columns: "exchange,symbol,base_coin,rate,interval_hours,settled_at",
|
|
41881
|
-
tsColumn: "settled_at"
|
|
41662
|
+
table: "core.funding_settlements",
|
|
41663
|
+
columns: "venue AS exchange, venue_symbol AS symbol, base_coin, rate, interval_hours, settled_at, apr AS funding_annualized",
|
|
41664
|
+
tsColumn: "settled_at",
|
|
41665
|
+
numeric: ["rate", "funding_annualized"]
|
|
41882
41666
|
},
|
|
41883
41667
|
open_interest: {
|
|
41884
|
-
table: "
|
|
41885
|
-
columns: "exchange,base_coin,symbol,open_interest_value,timestamp",
|
|
41886
|
-
tsColumn: "
|
|
41668
|
+
table: "core.open_interest_hourly",
|
|
41669
|
+
columns: "venue AS exchange, base_coin, venue_symbol AS symbol, oi_usd AS open_interest_value, hour AS timestamp",
|
|
41670
|
+
tsColumn: "hour",
|
|
41671
|
+
numeric: ["open_interest_value"]
|
|
41887
41672
|
},
|
|
41888
41673
|
basis: {
|
|
41889
|
-
table: "
|
|
41890
|
-
columns: "exchange,base_coin,spot_price,futures_price,basis_rate,
|
|
41891
|
-
tsColumn: "
|
|
41674
|
+
table: "core.basis",
|
|
41675
|
+
columns: "venue AS exchange, base_coin, spot_price, futures_price, basis_rate, basis_pct, ts AS timestamp",
|
|
41676
|
+
tsColumn: "ts",
|
|
41677
|
+
numeric: ["spot_price", "futures_price", "basis_rate", "basis_pct"]
|
|
41892
41678
|
},
|
|
41893
41679
|
long_short: {
|
|
41894
|
-
table: "
|
|
41895
|
-
columns: "exchange,base_coin,symbol,long_ratio,short_ratio,timestamp",
|
|
41896
|
-
tsColumn: "
|
|
41680
|
+
table: "core.long_short_ratio",
|
|
41681
|
+
columns: "venue AS exchange, base_coin, venue_symbol AS symbol, ratio_kind, long_ratio, short_ratio, ts AS timestamp",
|
|
41682
|
+
tsColumn: "ts",
|
|
41683
|
+
numeric: ["long_ratio", "short_ratio"]
|
|
41897
41684
|
},
|
|
41898
41685
|
liquidations: {
|
|
41899
|
-
table: "
|
|
41900
|
-
columns: "exchange,base_coin,symbol,side,quantity,price,timestamp",
|
|
41901
|
-
tsColumn: "
|
|
41686
|
+
table: "core.liquidations",
|
|
41687
|
+
columns: "venue AS exchange, base_coin, venue_symbol AS symbol, side, qty_base AS quantity, price, notional_usd, event_time AS timestamp",
|
|
41688
|
+
tsColumn: "event_time",
|
|
41689
|
+
numeric: ["quantity", "price", "notional_usd"]
|
|
41902
41690
|
}
|
|
41903
41691
|
};
|
|
41904
|
-
|
|
41692
|
+
var TOP_TRADER_SUFFIX = /_top_trader$/i;
|
|
41693
|
+
function toNumbers(row, keys) {
|
|
41694
|
+
const out = { ...row };
|
|
41695
|
+
for (const key of keys) {
|
|
41696
|
+
const value = out[key];
|
|
41697
|
+
if (typeof value === "string") out[key] = Number(value);
|
|
41698
|
+
}
|
|
41699
|
+
return out;
|
|
41700
|
+
}
|
|
41701
|
+
async function fetchHistoryRows(query, dataset, filters) {
|
|
41905
41702
|
const spec = HISTORY_TABLES[dataset];
|
|
41906
|
-
|
|
41703
|
+
const where = ["base_coin = {base_coin:String}"];
|
|
41704
|
+
const params = { base_coin: filters.base_coin };
|
|
41907
41705
|
if (filters.exchange) {
|
|
41908
|
-
|
|
41706
|
+
let venue = filters.exchange;
|
|
41707
|
+
if (dataset === "long_short" && TOP_TRADER_SUFFIX.test(venue)) {
|
|
41708
|
+
venue = venue.replace(TOP_TRADER_SUFFIX, "");
|
|
41709
|
+
where.push("ratio_kind != 'accounts'");
|
|
41710
|
+
}
|
|
41711
|
+
where.push("lower(venue) = lower({exchange:String})");
|
|
41712
|
+
params.exchange = venue;
|
|
41909
41713
|
}
|
|
41910
|
-
if (filters.from)
|
|
41911
|
-
|
|
41912
|
-
|
|
41913
|
-
|
|
41714
|
+
if (filters.from) {
|
|
41715
|
+
where.push(`${spec.tsColumn} >= fromUnixTimestamp64Milli({from_ms:Int64})`);
|
|
41716
|
+
params.from_ms = Date.parse(filters.from);
|
|
41717
|
+
}
|
|
41718
|
+
if (filters.to) {
|
|
41719
|
+
where.push(`${spec.tsColumn} <= fromUnixTimestamp64Milli({to_ms:Int64})`);
|
|
41720
|
+
params.to_ms = Date.parse(filters.to);
|
|
41721
|
+
}
|
|
41722
|
+
const sql = `SELECT ${spec.columns} FROM ${spec.table} FINAL WHERE ${where.join(" AND ")} ORDER BY ${spec.tsColumn} DESC LIMIT ${filters.limit}`;
|
|
41723
|
+
try {
|
|
41724
|
+
const rows = await query(sql, params);
|
|
41725
|
+
return rows.map((row) => toNumbers(row, spec.numeric));
|
|
41726
|
+
} catch (error) {
|
|
41914
41727
|
throw archiveQueryError(
|
|
41915
41728
|
`reading ${spec.table} for ${filters.base_coin}`,
|
|
41916
41729
|
error
|
|
41917
41730
|
);
|
|
41918
41731
|
}
|
|
41919
|
-
return data ?? [];
|
|
41920
41732
|
}
|
|
41921
41733
|
var coverageCache = /* @__PURE__ */ new Map();
|
|
41922
|
-
async function
|
|
41923
|
-
|
|
41924
|
-
|
|
41925
|
-
|
|
41926
|
-
|
|
41734
|
+
async function probeExtremes(query, dataset, baseCoin) {
|
|
41735
|
+
const spec = HISTORY_TABLES[dataset];
|
|
41736
|
+
const where = baseCoin ? " WHERE base_coin = {base_coin:String}" : "";
|
|
41737
|
+
try {
|
|
41738
|
+
const [row] = await query(
|
|
41739
|
+
`SELECT minOrNull(${spec.tsColumn}) AS earliest_at, maxOrNull(${spec.tsColumn}) AS latest_at FROM ${spec.table} FINAL${where}`,
|
|
41740
|
+
baseCoin ? { base_coin: baseCoin } : {}
|
|
41741
|
+
);
|
|
41742
|
+
return {
|
|
41743
|
+
dataset,
|
|
41744
|
+
table: spec.table,
|
|
41745
|
+
scope: baseCoin ? "probe_coin" : "all_coins",
|
|
41746
|
+
earliest_at: typeof row?.earliest_at === "string" ? row.earliest_at : null,
|
|
41747
|
+
latest_at: typeof row?.latest_at === "string" ? row.latest_at : null
|
|
41748
|
+
};
|
|
41749
|
+
} catch (error) {
|
|
41927
41750
|
throw archiveQueryError(`probing ${spec.table} coverage`, error);
|
|
41928
41751
|
}
|
|
41929
|
-
const row = (data ?? [])[0];
|
|
41930
|
-
const value = row?.[spec.tsColumn];
|
|
41931
|
-
return typeof value === "string" ? value : null;
|
|
41932
41752
|
}
|
|
41933
|
-
async function listFundingVenues(
|
|
41934
|
-
|
|
41935
|
-
|
|
41936
|
-
|
|
41937
|
-
|
|
41938
|
-
|
|
41939
|
-
|
|
41940
|
-
|
|
41941
|
-
|
|
41942
|
-
|
|
41943
|
-
|
|
41944
|
-
|
|
41945
|
-
venues.push(row.exchange);
|
|
41946
|
-
cursor = row.exchange;
|
|
41753
|
+
async function listFundingVenues(query) {
|
|
41754
|
+
try {
|
|
41755
|
+
const rows = await query(
|
|
41756
|
+
`SELECT DISTINCT venue FROM ${HISTORY_TABLES.funding.table} FINAL ORDER BY venue LIMIT ${VENUE_LIST_MAX + 1}`
|
|
41757
|
+
);
|
|
41758
|
+
const venues = rows.map((row) => row.venue).filter((venue) => typeof venue === "string");
|
|
41759
|
+
return {
|
|
41760
|
+
venues: venues.slice(0, VENUE_LIST_MAX),
|
|
41761
|
+
truncated: venues.length > VENUE_LIST_MAX
|
|
41762
|
+
};
|
|
41763
|
+
} catch (error) {
|
|
41764
|
+
throw archiveQueryError("enumerating archive venues", error);
|
|
41947
41765
|
}
|
|
41948
|
-
return { venues, truncated: true };
|
|
41949
41766
|
}
|
|
41950
|
-
async function fetchCoverage(
|
|
41767
|
+
async function fetchCoverage(query, probeBaseCoin) {
|
|
41951
41768
|
const cached = coverageCache.get(probeBaseCoin);
|
|
41952
41769
|
if (cached && Date.now() - cached.at < COVERAGE_CACHE_TTL_MS) {
|
|
41953
41770
|
return { coverage: cached.payload, cache: "hit" };
|
|
@@ -41958,40 +41775,20 @@ async function fetchCoverage(client2, probeBaseCoin) {
|
|
|
41958
41775
|
"long_short",
|
|
41959
41776
|
"liquidations"
|
|
41960
41777
|
];
|
|
41961
|
-
const [venueScan,
|
|
41962
|
-
listFundingVenues(
|
|
41963
|
-
|
|
41964
|
-
|
|
41965
|
-
|
|
41966
|
-
|
|
41967
|
-
probeTimestamp(client2, HISTORY_TABLES[dataset], "desc", probeBaseCoin)
|
|
41968
|
-
])
|
|
41778
|
+
const [venueScan, funding, ...futures] = await Promise.all([
|
|
41779
|
+
listFundingVenues(query),
|
|
41780
|
+
probeExtremes(query, "funding", null),
|
|
41781
|
+
...futuresDatasets.map(
|
|
41782
|
+
(dataset) => probeExtremes(query, dataset, probeBaseCoin)
|
|
41783
|
+
)
|
|
41969
41784
|
]);
|
|
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
41785
|
const coverage = {
|
|
41989
41786
|
venues: venueScan.venues,
|
|
41990
41787
|
venue_count: venueScan.venues.length,
|
|
41991
41788
|
venues_truncated: venueScan.truncated,
|
|
41992
41789
|
probe_base_coin: probeBaseCoin,
|
|
41993
|
-
datasets,
|
|
41994
|
-
note:
|
|
41790
|
+
datasets: [funding, ...futures],
|
|
41791
|
+
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
41792
|
};
|
|
41996
41793
|
if (coverageCache.size >= COVERAGE_CACHE_MAX_ENTRIES) {
|
|
41997
41794
|
const oldestKey = coverageCache.keys().next().value;
|
|
@@ -42077,21 +41874,21 @@ function missingCredentialsResult(tool101) {
|
|
|
42077
41874
|
true
|
|
42078
41875
|
);
|
|
42079
41876
|
}
|
|
41877
|
+
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
41878
|
function historyHandler(toolName, dataset, options) {
|
|
42081
41879
|
return async (_client, args) => {
|
|
42082
|
-
const
|
|
42083
|
-
if (!
|
|
41880
|
+
const query = getArchiveClient();
|
|
41881
|
+
if (!query) return missingCredentialsResult(toolName);
|
|
42084
41882
|
try {
|
|
42085
41883
|
const filters = parseHistoryArgs(args);
|
|
42086
|
-
const
|
|
42087
|
-
const mapped = options?.mapRow ? rows.map(options.mapRow) : rows;
|
|
41884
|
+
const mapped = await fetchHistoryRows(query, dataset, filters);
|
|
42088
41885
|
const spec = HISTORY_TABLES[dataset];
|
|
42089
41886
|
return asTextContentResult({
|
|
42090
41887
|
provider: PROVIDER_NAME,
|
|
42091
41888
|
dataset: spec.table,
|
|
42092
41889
|
runtime_status: "ok",
|
|
42093
41890
|
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42094
|
-
note: options?.extraNote ?
|
|
41891
|
+
note: options?.extraNote ? `${options.extraNote} ${HISTORY_NOTE}` : HISTORY_NOTE,
|
|
42095
41892
|
filters,
|
|
42096
41893
|
order: `${spec.tsColumn} desc`,
|
|
42097
41894
|
returnedCount: mapped.length,
|
|
@@ -42113,7 +41910,7 @@ function getAllArchiveEndpoints() {
|
|
|
42113
41910
|
},
|
|
42114
41911
|
tool: {
|
|
42115
41912
|
name: "archive_get_funding_history",
|
|
42116
|
-
description: `Get historical perp funding-rate SETTLEMENTS for one base coin across up to
|
|
41913
|
+
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
41914
|
|
|
42118
41915
|
${SHARED_ARGS_DOC}
|
|
42119
41916
|
|
|
@@ -42134,19 +41931,11 @@ Don't use when:
|
|
|
42134
41931
|
- You need one venue's own raw history endpoint (use get_funding_rate_history or hyperliquid_get_funding_history)
|
|
42135
41932
|
- You need tokenized-stock/RWA perp funding (use rwa_get_funding_rates)`,
|
|
42136
41933
|
inputSchema: historyInputSchema(
|
|
42137
|
-
'Base asset ticker to fetch settlements for, e.g. "BTC". Required
|
|
41934
|
+
'Base asset ticker to fetch settlements for, e.g. "BTC". Required.'
|
|
42138
41935
|
),
|
|
42139
41936
|
annotations: ARCHIVE_READ_ANNOTATIONS
|
|
42140
41937
|
},
|
|
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
|
-
})
|
|
41938
|
+
handler: historyHandler("archive_get_funding_history", "funding")
|
|
42150
41939
|
},
|
|
42151
41940
|
{
|
|
42152
41941
|
metadata: {
|
|
@@ -42162,7 +41951,7 @@ Don't use when:
|
|
|
42162
41951
|
},
|
|
42163
41952
|
tool: {
|
|
42164
41953
|
name: "archive_get_open_interest_history",
|
|
42165
|
-
description: `Get historical perp open-interest snapshots for one base coin across venues from the Hive Archive
|
|
41954
|
+
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
41955
|
|
|
42167
41956
|
${SHARED_ARGS_DOC}
|
|
42168
41957
|
|
|
@@ -42189,7 +41978,7 @@ Don't use when:
|
|
|
42189
41978
|
"archive_get_open_interest_history",
|
|
42190
41979
|
"open_interest",
|
|
42191
41980
|
{
|
|
42192
|
-
extraNote: "open_interest_value is
|
|
41981
|
+
extraNote: "open_interest_value is US dollars, one side counted, per contract."
|
|
42193
41982
|
}
|
|
42194
41983
|
)
|
|
42195
41984
|
},
|
|
@@ -42201,7 +41990,7 @@ Don't use when:
|
|
|
42201
41990
|
},
|
|
42202
41991
|
tool: {
|
|
42203
41992
|
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),
|
|
41993
|
+
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
41994
|
|
|
42206
41995
|
${SHARED_ARGS_DOC}
|
|
42207
41996
|
|
|
@@ -42240,7 +42029,7 @@ Don't use when:
|
|
|
42240
42029
|
},
|
|
42241
42030
|
tool: {
|
|
42242
42031
|
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.
|
|
42032
|
+
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
42033
|
|
|
42245
42034
|
${SHARED_ARGS_DOC}
|
|
42246
42035
|
|
|
@@ -42273,7 +42062,7 @@ Don't use when:
|
|
|
42273
42062
|
},
|
|
42274
42063
|
tool: {
|
|
42275
42064
|
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
|
|
42065
|
+
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
42066
|
|
|
42278
42067
|
${SHARED_ARGS_DOC}
|
|
42279
42068
|
|
|
@@ -42308,7 +42097,7 @@ Don't use when:
|
|
|
42308
42097
|
},
|
|
42309
42098
|
tool: {
|
|
42310
42099
|
name: "archive_get_coverage",
|
|
42311
|
-
description: `Discover what the Hive Archive holds: the
|
|
42100
|
+
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
42101
|
|
|
42313
42102
|
Args:
|
|
42314
42103
|
- base_coin (string, optional): probe coin for the futures datasets (default "BTC"). Pass another ticker to check archive depth for that asset.
|
|
@@ -42340,11 +42129,11 @@ Don't use when:
|
|
|
42340
42129
|
},
|
|
42341
42130
|
handler: async (_client, args) => {
|
|
42342
42131
|
const toolName = "archive_get_coverage";
|
|
42343
|
-
const
|
|
42344
|
-
if (!
|
|
42132
|
+
const query = getArchiveClient();
|
|
42133
|
+
if (!query) return missingCredentialsResult(toolName);
|
|
42345
42134
|
try {
|
|
42346
42135
|
const probe = typeof args?.base_coin === "string" && args.base_coin.trim() !== "" ? args.base_coin.trim().toUpperCase() : "BTC";
|
|
42347
|
-
const { coverage, cache } = await fetchCoverage(
|
|
42136
|
+
const { coverage, cache } = await fetchCoverage(query, probe);
|
|
42348
42137
|
return asTextContentResult({
|
|
42349
42138
|
provider: PROVIDER_NAME,
|
|
42350
42139
|
runtime_status: "ok",
|
|
@@ -43828,6 +43617,1376 @@ Don't use when:
|
|
|
43828
43617
|
];
|
|
43829
43618
|
}
|
|
43830
43619
|
|
|
43620
|
+
// src/tools/kalshi/client.ts
|
|
43621
|
+
var KALSHI_BASE_URL = "https://api.elections.kalshi.com/trade-api/v2";
|
|
43622
|
+
var KALSHI_USER_AGENT = "HiveIntelligence/1.0 (+https://hiveintelligence.xyz)";
|
|
43623
|
+
var KALSHI_TIMEOUT_MS = 1e4;
|
|
43624
|
+
var KALSHI_NETWORK_RETRIES = 1;
|
|
43625
|
+
var NETWORK_RETRY_DELAY_MS = 150;
|
|
43626
|
+
var UPSTREAM_ERROR_MAX_CHARS2 = 200;
|
|
43627
|
+
var KalshiInputError = class extends Error {
|
|
43628
|
+
};
|
|
43629
|
+
var KalshiNotFoundError = class extends Error {
|
|
43630
|
+
constructor(message, url) {
|
|
43631
|
+
super(message);
|
|
43632
|
+
this.url = url;
|
|
43633
|
+
}
|
|
43634
|
+
url;
|
|
43635
|
+
};
|
|
43636
|
+
function buildUrl2(path, params = {}, base = KALSHI_BASE_URL) {
|
|
43637
|
+
const url = new URL(`${base}${path}`);
|
|
43638
|
+
for (const [key, value] of Object.entries(params)) {
|
|
43639
|
+
if (value === void 0) continue;
|
|
43640
|
+
url.searchParams.set(key, String(value));
|
|
43641
|
+
}
|
|
43642
|
+
return url.toString();
|
|
43643
|
+
}
|
|
43644
|
+
function clip(text) {
|
|
43645
|
+
return text.trim().slice(0, UPSTREAM_ERROR_MAX_CHARS2);
|
|
43646
|
+
}
|
|
43647
|
+
function upstreamErrorText2(bodyText) {
|
|
43648
|
+
try {
|
|
43649
|
+
const parsed = JSON.parse(bodyText);
|
|
43650
|
+
if (parsed && typeof parsed === "object") {
|
|
43651
|
+
const err = parsed.error;
|
|
43652
|
+
if (typeof err === "string" && err.trim()) return clip(err);
|
|
43653
|
+
if (err && typeof err === "object") {
|
|
43654
|
+
const record = err;
|
|
43655
|
+
const parts = [record.code, record.message, record.details].filter(
|
|
43656
|
+
(part) => typeof part === "string" && part.trim() !== ""
|
|
43657
|
+
);
|
|
43658
|
+
if (parts.length > 0) return clip(parts.join(": "));
|
|
43659
|
+
}
|
|
43660
|
+
if (typeof parsed.msg === "string" && parsed.msg.trim()) {
|
|
43661
|
+
return clip(parsed.msg);
|
|
43662
|
+
}
|
|
43663
|
+
if (typeof parsed.message === "string" && parsed.message.trim()) {
|
|
43664
|
+
return clip(parsed.message);
|
|
43665
|
+
}
|
|
43666
|
+
}
|
|
43667
|
+
} catch {
|
|
43668
|
+
}
|
|
43669
|
+
const trimmed = bodyText.trim();
|
|
43670
|
+
return trimmed ? clip(trimmed) : "";
|
|
43671
|
+
}
|
|
43672
|
+
function describeNetworkError(error) {
|
|
43673
|
+
if (!(error instanceof Error)) return String(error);
|
|
43674
|
+
const cause = error.cause;
|
|
43675
|
+
const code = cause && typeof cause === "object" && typeof cause.code === "string" ? cause.code : null;
|
|
43676
|
+
return code ? `${error.message} (${code})` : error.message;
|
|
43677
|
+
}
|
|
43678
|
+
function sleep2(ms) {
|
|
43679
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
43680
|
+
}
|
|
43681
|
+
async function fetchKalshiJson(url, label, timeoutMs = KALSHI_TIMEOUT_MS) {
|
|
43682
|
+
let response = null;
|
|
43683
|
+
for (let attempt = 0; attempt <= KALSHI_NETWORK_RETRIES; attempt++) {
|
|
43684
|
+
const controller = new AbortController();
|
|
43685
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
43686
|
+
try {
|
|
43687
|
+
response = await fetch(url, {
|
|
43688
|
+
method: "GET",
|
|
43689
|
+
headers: {
|
|
43690
|
+
Accept: "application/json",
|
|
43691
|
+
"User-Agent": KALSHI_USER_AGENT
|
|
43692
|
+
},
|
|
43693
|
+
signal: controller.signal
|
|
43694
|
+
});
|
|
43695
|
+
break;
|
|
43696
|
+
} catch (error) {
|
|
43697
|
+
if (controller.signal.aborted) {
|
|
43698
|
+
throw new Error(
|
|
43699
|
+
`Kalshi ${label} timed out after ${timeoutMs} ms. Retry once; if it persists the upstream is degraded.`
|
|
43700
|
+
);
|
|
43701
|
+
}
|
|
43702
|
+
if (attempt < KALSHI_NETWORK_RETRIES) {
|
|
43703
|
+
await sleep2(NETWORK_RETRY_DELAY_MS);
|
|
43704
|
+
continue;
|
|
43705
|
+
}
|
|
43706
|
+
throw new Error(
|
|
43707
|
+
`Kalshi ${label} request failed after ${attempt + 1} attempts (${describeNetworkError(error)}); the upstream is temporarily unreachable. Retry shortly.`
|
|
43708
|
+
);
|
|
43709
|
+
} finally {
|
|
43710
|
+
clearTimeout(timer);
|
|
43711
|
+
}
|
|
43712
|
+
}
|
|
43713
|
+
if (!response) {
|
|
43714
|
+
throw new Error(`Kalshi ${label} produced no response.`);
|
|
43715
|
+
}
|
|
43716
|
+
const bodyText = await response.text();
|
|
43717
|
+
if (!response.ok) {
|
|
43718
|
+
const detail = upstreamErrorText2(bodyText);
|
|
43719
|
+
const suffix = detail ? `: ${detail}` : "";
|
|
43720
|
+
const statusText = response.statusText ? ` ${response.statusText}` : "";
|
|
43721
|
+
if (response.status === 404) {
|
|
43722
|
+
throw new KalshiNotFoundError(
|
|
43723
|
+
`Kalshi ${label} responded 404${suffix}`,
|
|
43724
|
+
url
|
|
43725
|
+
);
|
|
43726
|
+
}
|
|
43727
|
+
if (response.status === 400 || response.status === 422) {
|
|
43728
|
+
throw new KalshiInputError(
|
|
43729
|
+
`Invalid arguments: Kalshi ${label} rejected the request (${response.status})${suffix}.`
|
|
43730
|
+
);
|
|
43731
|
+
}
|
|
43732
|
+
if (response.status === 429) {
|
|
43733
|
+
throw new Error(
|
|
43734
|
+
`Kalshi ${label} responded 429 Too Many Requests${suffix}. Kalshi rate-limited this client; back off and retry.`
|
|
43735
|
+
);
|
|
43736
|
+
}
|
|
43737
|
+
throw new Error(
|
|
43738
|
+
`Kalshi ${label} responded ${response.status}${statusText}${suffix}`
|
|
43739
|
+
);
|
|
43740
|
+
}
|
|
43741
|
+
try {
|
|
43742
|
+
return JSON.parse(bodyText);
|
|
43743
|
+
} catch {
|
|
43744
|
+
throw new Error(`Kalshi ${label} returned a non-JSON body.`);
|
|
43745
|
+
}
|
|
43746
|
+
}
|
|
43747
|
+
|
|
43748
|
+
// src/tools/kalshi/api.ts
|
|
43749
|
+
var DEFAULT_LIMIT4 = 10;
|
|
43750
|
+
var MAX_LIMIT4 = 50;
|
|
43751
|
+
var DEFAULT_TRADES_LIMIT = 50;
|
|
43752
|
+
var MAX_TRADES_LIMIT = 200;
|
|
43753
|
+
var DEFAULT_BOOK_DEPTH2 = 10;
|
|
43754
|
+
var MAX_BOOK_DEPTH2 = 100;
|
|
43755
|
+
var SEARCH_PAGE_SIZE = 200;
|
|
43756
|
+
var SEARCH_MAX_PAGES = 3;
|
|
43757
|
+
var EVENT_MARKETS_PREVIEW2 = 25;
|
|
43758
|
+
var MAX_CANDLES = 500;
|
|
43759
|
+
var PERIOD_INTERVALS = [1, 60, 1440];
|
|
43760
|
+
var DEFAULT_PERIOD_INTERVAL = 60;
|
|
43761
|
+
var DEFAULT_WINDOW_CANDLES = {
|
|
43762
|
+
1: 480,
|
|
43763
|
+
60: 480,
|
|
43764
|
+
1440: 365
|
|
43765
|
+
};
|
|
43766
|
+
var MARKET_STATUS_FILTERS = [
|
|
43767
|
+
"unopened",
|
|
43768
|
+
"open",
|
|
43769
|
+
"paused",
|
|
43770
|
+
"closed",
|
|
43771
|
+
"settled"
|
|
43772
|
+
];
|
|
43773
|
+
var EVENT_STATUS_FILTERS = [
|
|
43774
|
+
"unopened",
|
|
43775
|
+
"open",
|
|
43776
|
+
"closed",
|
|
43777
|
+
"settled"
|
|
43778
|
+
];
|
|
43779
|
+
var ANY_STATUS = "all";
|
|
43780
|
+
var TICKER_RE = /^[A-Z0-9][A-Z0-9._-]{0,119}$/;
|
|
43781
|
+
var QUERY_MAX_CHARS2 = 200;
|
|
43782
|
+
var CURSOR_MAX_CHARS = 2e3;
|
|
43783
|
+
var RULES_MAX_CHARS = 1200;
|
|
43784
|
+
var MS_THRESHOLD = 1e11;
|
|
43785
|
+
function invalid2(message) {
|
|
43786
|
+
throw new KalshiInputError(`Invalid arguments: ${message}`);
|
|
43787
|
+
}
|
|
43788
|
+
function parseFiniteNumber3(value) {
|
|
43789
|
+
const num = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
43790
|
+
return Number.isFinite(num) ? num : null;
|
|
43791
|
+
}
|
|
43792
|
+
function asRecord2(value) {
|
|
43793
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
43794
|
+
}
|
|
43795
|
+
function asRecordArray2(value, label) {
|
|
43796
|
+
if (!Array.isArray(value)) {
|
|
43797
|
+
throw new Error(
|
|
43798
|
+
`Kalshi ${label} returned an unexpected payload shape (expected a JSON array).`
|
|
43799
|
+
);
|
|
43800
|
+
}
|
|
43801
|
+
return value.filter((item) => asRecord2(item) !== null);
|
|
43802
|
+
}
|
|
43803
|
+
function optionalString2(value) {
|
|
43804
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
43805
|
+
}
|
|
43806
|
+
function optionalBoolean2(value) {
|
|
43807
|
+
return typeof value === "boolean" ? value : null;
|
|
43808
|
+
}
|
|
43809
|
+
function round4(value) {
|
|
43810
|
+
return Number(value.toFixed(4));
|
|
43811
|
+
}
|
|
43812
|
+
function dollarsToCents(dollars) {
|
|
43813
|
+
return Math.round(dollars * 1e4) / 100;
|
|
43814
|
+
}
|
|
43815
|
+
function readPrice(raw, base) {
|
|
43816
|
+
const dollars = parseFiniteNumber3(raw[`${base}_dollars`]);
|
|
43817
|
+
if (dollars !== null) return { dollars, cents: dollarsToCents(dollars) };
|
|
43818
|
+
const legacyCents = parseFiniteNumber3(raw[base]);
|
|
43819
|
+
if (legacyCents !== null) {
|
|
43820
|
+
return { dollars: round4(legacyCents / 100), cents: legacyCents };
|
|
43821
|
+
}
|
|
43822
|
+
return { dollars: null, cents: null };
|
|
43823
|
+
}
|
|
43824
|
+
function readCount(raw, base) {
|
|
43825
|
+
const fp = parseFiniteNumber3(raw[`${base}_fp`]);
|
|
43826
|
+
return fp !== null ? fp : parseFiniteNumber3(raw[base]);
|
|
43827
|
+
}
|
|
43828
|
+
function unixSecondsToIso2(seconds) {
|
|
43829
|
+
return new Date(seconds * 1e3).toISOString();
|
|
43830
|
+
}
|
|
43831
|
+
function boundedText(value, max) {
|
|
43832
|
+
const text = optionalString2(value);
|
|
43833
|
+
if (!text) return null;
|
|
43834
|
+
return text.length > max ? `${text.slice(0, max)}...` : text;
|
|
43835
|
+
}
|
|
43836
|
+
function parseBoundedInteger2(value, field, min, max, fallback) {
|
|
43837
|
+
if (value === void 0 || value === null) return fallback;
|
|
43838
|
+
const num = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
43839
|
+
if (!Number.isInteger(num) || num < min || num > max) {
|
|
43840
|
+
invalid2(
|
|
43841
|
+
`${field} must be an integer between ${min} and ${max}; got ${JSON.stringify(value)}.`
|
|
43842
|
+
);
|
|
43843
|
+
}
|
|
43844
|
+
return num;
|
|
43845
|
+
}
|
|
43846
|
+
function parseLimit2(value) {
|
|
43847
|
+
return parseBoundedInteger2(value, "limit", 1, MAX_LIMIT4, DEFAULT_LIMIT4);
|
|
43848
|
+
}
|
|
43849
|
+
function parseTradesLimit(value) {
|
|
43850
|
+
return parseBoundedInteger2(
|
|
43851
|
+
value,
|
|
43852
|
+
"limit",
|
|
43853
|
+
1,
|
|
43854
|
+
MAX_TRADES_LIMIT,
|
|
43855
|
+
DEFAULT_TRADES_LIMIT
|
|
43856
|
+
);
|
|
43857
|
+
}
|
|
43858
|
+
function parseBookDepth2(value) {
|
|
43859
|
+
return parseBoundedInteger2(
|
|
43860
|
+
value,
|
|
43861
|
+
"depth",
|
|
43862
|
+
1,
|
|
43863
|
+
MAX_BOOK_DEPTH2,
|
|
43864
|
+
DEFAULT_BOOK_DEPTH2
|
|
43865
|
+
);
|
|
43866
|
+
}
|
|
43867
|
+
function parseOptionalBoolean2(value, field, fallback) {
|
|
43868
|
+
if (value === void 0 || value === null) return fallback;
|
|
43869
|
+
if (typeof value === "boolean") return value;
|
|
43870
|
+
if (value === "true") return true;
|
|
43871
|
+
if (value === "false") return false;
|
|
43872
|
+
return invalid2(`${field} must be a boolean; got ${JSON.stringify(value)}.`);
|
|
43873
|
+
}
|
|
43874
|
+
function parseOptionalQuery2(value) {
|
|
43875
|
+
if (value === void 0 || value === null) return null;
|
|
43876
|
+
if (typeof value !== "string") {
|
|
43877
|
+
invalid2(`query must be a string; got ${JSON.stringify(value)}.`);
|
|
43878
|
+
}
|
|
43879
|
+
const query = value.trim();
|
|
43880
|
+
if (!query) return null;
|
|
43881
|
+
if (query.length > QUERY_MAX_CHARS2) {
|
|
43882
|
+
invalid2(`query must be at most ${QUERY_MAX_CHARS2} characters.`);
|
|
43883
|
+
}
|
|
43884
|
+
return query;
|
|
43885
|
+
}
|
|
43886
|
+
function parseOptionalCursor(value) {
|
|
43887
|
+
if (value === void 0 || value === null) return null;
|
|
43888
|
+
const cursor = typeof value === "string" ? value.trim() : "";
|
|
43889
|
+
if (!cursor) return null;
|
|
43890
|
+
if (cursor.length > CURSOR_MAX_CHARS) {
|
|
43891
|
+
invalid2(
|
|
43892
|
+
`cursor must be the opaque next_cursor string from a previous response (at most ${CURSOR_MAX_CHARS} characters).`
|
|
43893
|
+
);
|
|
43894
|
+
}
|
|
43895
|
+
return cursor;
|
|
43896
|
+
}
|
|
43897
|
+
function parseOptionalTicker(value, field) {
|
|
43898
|
+
if (value === void 0 || value === null) return null;
|
|
43899
|
+
const ticker = typeof value === "string" ? value.trim().toUpperCase() : "";
|
|
43900
|
+
if (ticker === "") return null;
|
|
43901
|
+
if (!TICKER_RE.test(ticker)) {
|
|
43902
|
+
invalid2(
|
|
43903
|
+
`${field} must be a Kalshi ticker (letters, digits, "-", ".", "_"), e.g. "KXBTCD-26SEP1612-T85799.99"; got ${JSON.stringify(value)}.`
|
|
43904
|
+
);
|
|
43905
|
+
}
|
|
43906
|
+
return ticker;
|
|
43907
|
+
}
|
|
43908
|
+
function parseRequiredTicker(value, field = "ticker") {
|
|
43909
|
+
const ticker = parseOptionalTicker(value, field);
|
|
43910
|
+
if (!ticker) {
|
|
43911
|
+
invalid2(
|
|
43912
|
+
`${field} is required. Pass a Kalshi market ticker such as "KXNIREFCALL-30JAN01-YES" (find one with kalshi_search_markets or kalshi_get_events).`
|
|
43913
|
+
);
|
|
43914
|
+
}
|
|
43915
|
+
return ticker;
|
|
43916
|
+
}
|
|
43917
|
+
function parseStatusFilter(value, allowed, fallback) {
|
|
43918
|
+
if (value === void 0 || value === null) return fallback;
|
|
43919
|
+
const status = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
43920
|
+
if (status === ANY_STATUS) return null;
|
|
43921
|
+
if (!allowed.includes(status)) {
|
|
43922
|
+
invalid2(
|
|
43923
|
+
`status must be one of ${[...allowed, ANY_STATUS].map((item) => `"${item}"`).join(", ")}; got ${JSON.stringify(value)}.`
|
|
43924
|
+
);
|
|
43925
|
+
}
|
|
43926
|
+
return status;
|
|
43927
|
+
}
|
|
43928
|
+
function parsePeriodInterval(value) {
|
|
43929
|
+
if (value === void 0 || value === null) return DEFAULT_PERIOD_INTERVAL;
|
|
43930
|
+
const num = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
43931
|
+
if (!PERIOD_INTERVALS.includes(num)) {
|
|
43932
|
+
invalid2(
|
|
43933
|
+
`period_interval must be one of ${PERIOD_INTERVALS.join(", ")} (minutes per candle); got ${JSON.stringify(value)}.`
|
|
43934
|
+
);
|
|
43935
|
+
}
|
|
43936
|
+
return num;
|
|
43937
|
+
}
|
|
43938
|
+
function parseTimestamp(value, field) {
|
|
43939
|
+
if (value === void 0 || value === null) return null;
|
|
43940
|
+
if (typeof value === "string" && value.trim() === "") return null;
|
|
43941
|
+
const numeric = parseFiniteNumber3(value);
|
|
43942
|
+
if (numeric !== null) {
|
|
43943
|
+
if (numeric <= 0) {
|
|
43944
|
+
invalid2(`${field} must be a positive unix timestamp or an ISO 8601 datetime; got ${JSON.stringify(value)}.`);
|
|
43945
|
+
}
|
|
43946
|
+
return Math.floor(numeric >= MS_THRESHOLD ? numeric / 1e3 : numeric);
|
|
43947
|
+
}
|
|
43948
|
+
if (typeof value === "string") {
|
|
43949
|
+
const ms = Date.parse(value);
|
|
43950
|
+
if (Number.isFinite(ms)) return Math.floor(ms / 1e3);
|
|
43951
|
+
}
|
|
43952
|
+
return invalid2(
|
|
43953
|
+
`${field} must be an ISO 8601 datetime (e.g. "2026-09-01T00:00:00Z") or a unix timestamp in seconds; got ${JSON.stringify(value)}.`
|
|
43954
|
+
);
|
|
43955
|
+
}
|
|
43956
|
+
function resolveCandleWindow(args, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
43957
|
+
const interval = parsePeriodInterval(args?.period_interval);
|
|
43958
|
+
const intervalSeconds = interval * 60;
|
|
43959
|
+
const end = parseTimestamp(args?.end, "end") ?? nowSeconds;
|
|
43960
|
+
const requestedStart = parseTimestamp(args?.start, "start");
|
|
43961
|
+
let start = requestedStart ?? end - DEFAULT_WINDOW_CANDLES[interval] * intervalSeconds;
|
|
43962
|
+
if (start >= end) {
|
|
43963
|
+
invalid2(
|
|
43964
|
+
`start (${unixSecondsToIso2(start)}) must be before end (${unixSecondsToIso2(end)}).`
|
|
43965
|
+
);
|
|
43966
|
+
}
|
|
43967
|
+
const maxSpan = MAX_CANDLES * intervalSeconds;
|
|
43968
|
+
let truncated = false;
|
|
43969
|
+
if (end - start > maxSpan) {
|
|
43970
|
+
start = end - maxSpan;
|
|
43971
|
+
truncated = true;
|
|
43972
|
+
}
|
|
43973
|
+
return {
|
|
43974
|
+
start_ts: start,
|
|
43975
|
+
end_ts: end,
|
|
43976
|
+
period_interval: interval,
|
|
43977
|
+
requested_start_ts: requestedStart,
|
|
43978
|
+
window_truncated: truncated,
|
|
43979
|
+
expected_candles: Math.ceil((end - start) / intervalSeconds)
|
|
43980
|
+
};
|
|
43981
|
+
}
|
|
43982
|
+
function matchesQuery2(fields, query) {
|
|
43983
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
43984
|
+
if (terms.length === 0) return true;
|
|
43985
|
+
const text = fields.filter((value) => typeof value === "string").join(" ").toLowerCase();
|
|
43986
|
+
return terms.every((term) => text.includes(term));
|
|
43987
|
+
}
|
|
43988
|
+
function seriesTickerFromTicker(ticker) {
|
|
43989
|
+
const [head] = ticker.split("-");
|
|
43990
|
+
return head || ticker;
|
|
43991
|
+
}
|
|
43992
|
+
function toMarketRow3(raw) {
|
|
43993
|
+
const yesBid = readPrice(raw, "yes_bid");
|
|
43994
|
+
const yesAsk = readPrice(raw, "yes_ask");
|
|
43995
|
+
const last = readPrice(raw, "last_price");
|
|
43996
|
+
return {
|
|
43997
|
+
ticker: optionalString2(raw.ticker) ?? "",
|
|
43998
|
+
event_ticker: optionalString2(raw.event_ticker),
|
|
43999
|
+
title: optionalString2(raw.title),
|
|
44000
|
+
yes_sub_title: optionalString2(raw.yes_sub_title),
|
|
44001
|
+
no_sub_title: optionalString2(raw.no_sub_title),
|
|
44002
|
+
status: optionalString2(raw.status),
|
|
44003
|
+
market_type: optionalString2(raw.market_type),
|
|
44004
|
+
yes_bid: yesBid.dollars,
|
|
44005
|
+
yes_bid_cents: yesBid.cents,
|
|
44006
|
+
yes_ask: yesAsk.dollars,
|
|
44007
|
+
yes_ask_cents: yesAsk.cents,
|
|
44008
|
+
last_price: last.dollars,
|
|
44009
|
+
last_price_cents: last.cents,
|
|
44010
|
+
volume: readCount(raw, "volume"),
|
|
44011
|
+
volume_24h: readCount(raw, "volume_24h"),
|
|
44012
|
+
open_interest: readCount(raw, "open_interest"),
|
|
44013
|
+
open_time: optionalString2(raw.open_time),
|
|
44014
|
+
close_time: optionalString2(raw.close_time)
|
|
44015
|
+
};
|
|
44016
|
+
}
|
|
44017
|
+
function normalizeMarket(raw) {
|
|
44018
|
+
const row = toMarketRow3(raw);
|
|
44019
|
+
const noBid = readPrice(raw, "no_bid");
|
|
44020
|
+
const noAsk = readPrice(raw, "no_ask");
|
|
44021
|
+
const previous = readPrice(raw, "previous_price");
|
|
44022
|
+
const midpoint = row.yes_bid !== null && row.yes_ask !== null ? round4((row.yes_bid + row.yes_ask) / 2) : null;
|
|
44023
|
+
const spreadCents = row.yes_bid_cents !== null && row.yes_ask_cents !== null ? Number((row.yes_ask_cents - row.yes_bid_cents).toFixed(2)) : null;
|
|
44024
|
+
return {
|
|
44025
|
+
...row,
|
|
44026
|
+
series_ticker: seriesTickerFromTicker(row.ticker),
|
|
44027
|
+
subtitle: optionalString2(raw.subtitle),
|
|
44028
|
+
result: optionalString2(raw.result),
|
|
44029
|
+
no_bid: noBid.dollars,
|
|
44030
|
+
no_bid_cents: noBid.cents,
|
|
44031
|
+
no_ask: noAsk.dollars,
|
|
44032
|
+
no_ask_cents: noAsk.cents,
|
|
44033
|
+
yes_midpoint: midpoint,
|
|
44034
|
+
yes_spread_cents: spreadCents,
|
|
44035
|
+
previous_price: previous.dollars,
|
|
44036
|
+
previous_price_cents: previous.cents,
|
|
44037
|
+
previous_yes_bid: readPrice(raw, "previous_yes_bid").dollars,
|
|
44038
|
+
previous_yes_ask: readPrice(raw, "previous_yes_ask").dollars,
|
|
44039
|
+
yes_bid_size: readCount(raw, "yes_bid_size"),
|
|
44040
|
+
yes_ask_size: readCount(raw, "yes_ask_size"),
|
|
44041
|
+
liquidity_dollars: parseFiniteNumber3(raw.liquidity_dollars),
|
|
44042
|
+
notional_value_dollars: parseFiniteNumber3(raw.notional_value_dollars),
|
|
44043
|
+
expiration_time: optionalString2(raw.expiration_time),
|
|
44044
|
+
expected_expiration_time: optionalString2(raw.expected_expiration_time),
|
|
44045
|
+
latest_expiration_time: optionalString2(raw.latest_expiration_time),
|
|
44046
|
+
can_close_early: optionalBoolean2(raw.can_close_early),
|
|
44047
|
+
early_close_condition: optionalString2(raw.early_close_condition),
|
|
44048
|
+
settlement_timer_seconds: parseFiniteNumber3(raw.settlement_timer_seconds),
|
|
44049
|
+
strike_type: optionalString2(raw.strike_type),
|
|
44050
|
+
floor_strike: parseFiniteNumber3(raw.floor_strike),
|
|
44051
|
+
cap_strike: parseFiniteNumber3(raw.cap_strike),
|
|
44052
|
+
custom_strike: asRecord2(raw.custom_strike),
|
|
44053
|
+
is_provisional: optionalBoolean2(raw.is_provisional),
|
|
44054
|
+
mve_collection_ticker: optionalString2(raw.mve_collection_ticker),
|
|
44055
|
+
mve_selected_legs: Array.isArray(raw.mve_selected_legs) ? raw.mve_selected_legs : null,
|
|
44056
|
+
price_level_structure: optionalString2(raw.price_level_structure),
|
|
44057
|
+
price_ranges: Array.isArray(raw.price_ranges) ? raw.price_ranges : null,
|
|
44058
|
+
rules_primary: boundedText(raw.rules_primary, RULES_MAX_CHARS),
|
|
44059
|
+
rules_secondary: boundedText(raw.rules_secondary, RULES_MAX_CHARS),
|
|
44060
|
+
created_time: optionalString2(raw.created_time),
|
|
44061
|
+
updated_time: optionalString2(raw.updated_time)
|
|
44062
|
+
};
|
|
44063
|
+
}
|
|
44064
|
+
function toEventRow2(raw, withNested, extraMarkets = void 0) {
|
|
44065
|
+
const rawMarkets = Array.isArray(raw.markets) ? raw.markets : Array.isArray(extraMarkets) ? extraMarkets : [];
|
|
44066
|
+
const markets = rawMarkets.map(asRecord2).filter((item) => item !== null).map(toMarketRow3);
|
|
44067
|
+
const sources = Array.isArray(raw.settlement_sources) ? raw.settlement_sources.map((source) => {
|
|
44068
|
+
const record = asRecord2(source);
|
|
44069
|
+
return record ? optionalString2(record.name) : optionalString2(source);
|
|
44070
|
+
}).filter((name) => name !== null) : [];
|
|
44071
|
+
return {
|
|
44072
|
+
event_ticker: optionalString2(raw.event_ticker) ?? "",
|
|
44073
|
+
series_ticker: optionalString2(raw.series_ticker),
|
|
44074
|
+
title: optionalString2(raw.title),
|
|
44075
|
+
sub_title: optionalString2(raw.sub_title),
|
|
44076
|
+
category: optionalString2(raw.category),
|
|
44077
|
+
mutually_exclusive: optionalBoolean2(raw.mutually_exclusive),
|
|
44078
|
+
strike_date: optionalString2(raw.strike_date),
|
|
44079
|
+
strike_period: optionalString2(raw.strike_period),
|
|
44080
|
+
collateral_return_type: optionalString2(raw.collateral_return_type),
|
|
44081
|
+
settlement_sources: sources,
|
|
44082
|
+
last_updated_ts: optionalString2(raw.last_updated_ts),
|
|
44083
|
+
market_count: withNested ? markets.length : null,
|
|
44084
|
+
markets: withNested ? markets.slice(0, EVENT_MARKETS_PREVIEW2) : null,
|
|
44085
|
+
markets_truncated: withNested && markets.length > EVENT_MARKETS_PREVIEW2
|
|
44086
|
+
};
|
|
44087
|
+
}
|
|
44088
|
+
function parseLevels2(value, unit) {
|
|
44089
|
+
if (!Array.isArray(value)) return [];
|
|
44090
|
+
const levels = [];
|
|
44091
|
+
for (const item of value) {
|
|
44092
|
+
if (!Array.isArray(item) || item.length < 2) continue;
|
|
44093
|
+
const rawPrice = parseFiniteNumber3(item[0]);
|
|
44094
|
+
const size = parseFiniteNumber3(item[1]);
|
|
44095
|
+
if (rawPrice === null || size === null) continue;
|
|
44096
|
+
if (unit === "dollars") {
|
|
44097
|
+
levels.push({ price: rawPrice, cents: dollarsToCents(rawPrice), size });
|
|
44098
|
+
} else {
|
|
44099
|
+
levels.push({ price: round4(rawPrice / 100), cents: rawPrice, size });
|
|
44100
|
+
}
|
|
44101
|
+
}
|
|
44102
|
+
return levels.sort((a, b) => b.price - a.price);
|
|
44103
|
+
}
|
|
44104
|
+
function summarizeOrderbook2(raw, ticker, depth) {
|
|
44105
|
+
const fp = asRecord2(raw.orderbook_fp);
|
|
44106
|
+
const legacy = asRecord2(raw.orderbook);
|
|
44107
|
+
const yes = fp ? parseLevels2(fp.yes_dollars, "dollars") : parseLevels2(legacy?.yes, "cents");
|
|
44108
|
+
const no = fp ? parseLevels2(fp.no_dollars, "dollars") : parseLevels2(legacy?.no, "cents");
|
|
44109
|
+
const bestYesBid = yes[0]?.price ?? null;
|
|
44110
|
+
const bestNoBid = no[0]?.price ?? null;
|
|
44111
|
+
const bestYesAsk = bestNoBid !== null ? round4(1 - bestNoBid) : null;
|
|
44112
|
+
const bestNoAsk = bestYesBid !== null ? round4(1 - bestYesBid) : null;
|
|
44113
|
+
const spread = bestYesBid !== null && bestYesAsk !== null ? round4(bestYesAsk - bestYesBid) : null;
|
|
44114
|
+
const midpoint = bestYesBid !== null && bestYesAsk !== null ? round4((bestYesAsk + bestYesBid) / 2) : null;
|
|
44115
|
+
return {
|
|
44116
|
+
ticker,
|
|
44117
|
+
yes_bids: yes.slice(0, depth),
|
|
44118
|
+
no_bids: no.slice(0, depth),
|
|
44119
|
+
yes_levels_total: yes.length,
|
|
44120
|
+
no_levels_total: no.length,
|
|
44121
|
+
best_yes_bid: bestYesBid,
|
|
44122
|
+
best_yes_bid_cents: bestYesBid !== null ? dollarsToCents(bestYesBid) : null,
|
|
44123
|
+
best_yes_ask: bestYesAsk,
|
|
44124
|
+
best_yes_ask_cents: bestYesAsk !== null ? dollarsToCents(bestYesAsk) : null,
|
|
44125
|
+
best_no_bid: bestNoBid,
|
|
44126
|
+
best_no_ask: bestNoAsk,
|
|
44127
|
+
yes_spread: spread,
|
|
44128
|
+
yes_midpoint: midpoint
|
|
44129
|
+
};
|
|
44130
|
+
}
|
|
44131
|
+
function toTradeRow(raw) {
|
|
44132
|
+
const yesPrice2 = readPrice(raw, "yes_price");
|
|
44133
|
+
const noPrice = readPrice(raw, "no_price");
|
|
44134
|
+
return {
|
|
44135
|
+
trade_id: optionalString2(raw.trade_id),
|
|
44136
|
+
ticker: optionalString2(raw.ticker),
|
|
44137
|
+
created_time: optionalString2(raw.created_time),
|
|
44138
|
+
count: readCount(raw, "count"),
|
|
44139
|
+
yes_price: yesPrice2.dollars,
|
|
44140
|
+
yes_price_cents: yesPrice2.cents,
|
|
44141
|
+
no_price: noPrice.dollars,
|
|
44142
|
+
no_price_cents: noPrice.cents,
|
|
44143
|
+
taker_side: optionalString2(raw.taker_side),
|
|
44144
|
+
taker_book_side: optionalString2(raw.taker_book_side),
|
|
44145
|
+
is_block_trade: optionalBoolean2(raw.is_block_trade)
|
|
44146
|
+
};
|
|
44147
|
+
}
|
|
44148
|
+
function toCandle(raw) {
|
|
44149
|
+
const endTs = parseFiniteNumber3(raw.end_period_ts);
|
|
44150
|
+
if (endTs === null) return null;
|
|
44151
|
+
const price = asRecord2(raw.price) ?? {};
|
|
44152
|
+
const yesBid = asRecord2(raw.yes_bid) ?? {};
|
|
44153
|
+
const yesAsk = asRecord2(raw.yes_ask) ?? {};
|
|
44154
|
+
return {
|
|
44155
|
+
end_period_ts: endTs,
|
|
44156
|
+
end_time: unixSecondsToIso2(endTs),
|
|
44157
|
+
open: readPrice(price, "open").dollars,
|
|
44158
|
+
high: readPrice(price, "high").dollars,
|
|
44159
|
+
low: readPrice(price, "low").dollars,
|
|
44160
|
+
close: readPrice(price, "close").dollars,
|
|
44161
|
+
mean: readPrice(price, "mean").dollars,
|
|
44162
|
+
previous: readPrice(price, "previous").dollars,
|
|
44163
|
+
yes_bid_close: readPrice(yesBid, "close").dollars,
|
|
44164
|
+
yes_ask_close: readPrice(yesAsk, "close").dollars,
|
|
44165
|
+
volume: readCount(raw, "volume"),
|
|
44166
|
+
open_interest: readCount(raw, "open_interest")
|
|
44167
|
+
};
|
|
44168
|
+
}
|
|
44169
|
+
function summarizeCandles(candles) {
|
|
44170
|
+
const traded = candles.filter(
|
|
44171
|
+
(candle) => candle.close !== null
|
|
44172
|
+
);
|
|
44173
|
+
if (traded.length === 0) return null;
|
|
44174
|
+
const first = traded[0];
|
|
44175
|
+
const last = traded[traded.length - 1];
|
|
44176
|
+
let minLow = first.close;
|
|
44177
|
+
let maxHigh = first.close;
|
|
44178
|
+
let totalVolume = 0;
|
|
44179
|
+
let lastOpenInterest = null;
|
|
44180
|
+
for (const candle of candles) {
|
|
44181
|
+
const low = candle.low ?? candle.close;
|
|
44182
|
+
const high = candle.high ?? candle.close;
|
|
44183
|
+
if (low !== null && low < minLow) minLow = low;
|
|
44184
|
+
if (high !== null && high > maxHigh) maxHigh = high;
|
|
44185
|
+
if (candle.volume !== null) totalVolume += candle.volume;
|
|
44186
|
+
if (candle.open_interest !== null) lastOpenInterest = candle.open_interest;
|
|
44187
|
+
}
|
|
44188
|
+
const change = round4(last.close - first.close);
|
|
44189
|
+
return {
|
|
44190
|
+
candles: candles.length,
|
|
44191
|
+
candles_with_trades: traded.length,
|
|
44192
|
+
first_at: first.end_time,
|
|
44193
|
+
last_at: last.end_time,
|
|
44194
|
+
first_close: first.close,
|
|
44195
|
+
first_close_cents: dollarsToCents(first.close),
|
|
44196
|
+
last_close: last.close,
|
|
44197
|
+
last_close_cents: dollarsToCents(last.close),
|
|
44198
|
+
min_low: minLow,
|
|
44199
|
+
max_high: maxHigh,
|
|
44200
|
+
change,
|
|
44201
|
+
change_pct: first.close > 0 ? Number((change / first.close * 100).toFixed(2)) : null,
|
|
44202
|
+
total_volume: Number(totalVolume.toFixed(2)),
|
|
44203
|
+
last_open_interest: lastOpenInterest
|
|
44204
|
+
};
|
|
44205
|
+
}
|
|
44206
|
+
function readCursor(record) {
|
|
44207
|
+
const cursor = record ? optionalString2(record.cursor) : null;
|
|
44208
|
+
return cursor;
|
|
44209
|
+
}
|
|
44210
|
+
async function fetchMarketsPage(params) {
|
|
44211
|
+
const url = buildUrl2("/markets", params);
|
|
44212
|
+
const data = await fetchKalshiJson(url, "GET /markets");
|
|
44213
|
+
const record = asRecord2(data);
|
|
44214
|
+
return {
|
|
44215
|
+
url,
|
|
44216
|
+
rows: asRecordArray2(record?.markets, "GET /markets"),
|
|
44217
|
+
nextCursor: readCursor(record)
|
|
44218
|
+
};
|
|
44219
|
+
}
|
|
44220
|
+
async function searchMarkets(filters) {
|
|
44221
|
+
const pageSize = filters.query ? SEARCH_PAGE_SIZE : filters.limit;
|
|
44222
|
+
const maxPages = filters.query ? SEARCH_MAX_PAGES : 1;
|
|
44223
|
+
let cursor = filters.cursor;
|
|
44224
|
+
let firstUrl = "";
|
|
44225
|
+
let scanned = 0;
|
|
44226
|
+
let pagesScanned = 0;
|
|
44227
|
+
const matched = [];
|
|
44228
|
+
for (let page = 0; page < maxPages; page++) {
|
|
44229
|
+
const { url, rows, nextCursor } = await fetchMarketsPage({
|
|
44230
|
+
status: filters.status ?? void 0,
|
|
44231
|
+
series_ticker: filters.seriesTicker ?? void 0,
|
|
44232
|
+
event_ticker: filters.eventTicker ?? void 0,
|
|
44233
|
+
mve_filter: filters.eventTicker ? void 0 : "exclude",
|
|
44234
|
+
limit: pageSize,
|
|
44235
|
+
cursor: cursor ?? void 0
|
|
44236
|
+
});
|
|
44237
|
+
if (!firstUrl) firstUrl = url;
|
|
44238
|
+
pagesScanned += 1;
|
|
44239
|
+
scanned += rows.length;
|
|
44240
|
+
for (const raw of rows) {
|
|
44241
|
+
const row = toMarketRow3(raw);
|
|
44242
|
+
if (!filters.query || matchesQuery2(
|
|
44243
|
+
[
|
|
44244
|
+
row.ticker,
|
|
44245
|
+
row.event_ticker,
|
|
44246
|
+
row.title,
|
|
44247
|
+
row.yes_sub_title,
|
|
44248
|
+
row.no_sub_title
|
|
44249
|
+
],
|
|
44250
|
+
filters.query
|
|
44251
|
+
)) {
|
|
44252
|
+
matched.push(row);
|
|
44253
|
+
}
|
|
44254
|
+
}
|
|
44255
|
+
cursor = nextCursor;
|
|
44256
|
+
if (!cursor) break;
|
|
44257
|
+
if (matched.length >= filters.limit) break;
|
|
44258
|
+
}
|
|
44259
|
+
return {
|
|
44260
|
+
url: firstUrl,
|
|
44261
|
+
rows: matched.slice(0, filters.limit),
|
|
44262
|
+
pageSize,
|
|
44263
|
+
pagesScanned,
|
|
44264
|
+
scanned,
|
|
44265
|
+
matched: matched.length,
|
|
44266
|
+
nextCursor: cursor,
|
|
44267
|
+
mayHaveMore: matched.length > filters.limit || cursor !== null
|
|
44268
|
+
};
|
|
44269
|
+
}
|
|
44270
|
+
async function fetchMarket(ticker) {
|
|
44271
|
+
const url = buildUrl2(`/markets/${encodeURIComponent(ticker)}`);
|
|
44272
|
+
try {
|
|
44273
|
+
const data = await fetchKalshiJson(url, "GET /markets/{ticker}");
|
|
44274
|
+
const record = asRecord2(asRecord2(data)?.market);
|
|
44275
|
+
if (!record || typeof record.ticker !== "string") {
|
|
44276
|
+
throw new Error(
|
|
44277
|
+
"Kalshi GET /markets/{ticker} returned an unexpected payload shape."
|
|
44278
|
+
);
|
|
44279
|
+
}
|
|
44280
|
+
return { url, market: normalizeMarket(record) };
|
|
44281
|
+
} catch (error) {
|
|
44282
|
+
if (error instanceof KalshiNotFoundError) {
|
|
44283
|
+
invalid2(
|
|
44284
|
+
`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.`
|
|
44285
|
+
);
|
|
44286
|
+
}
|
|
44287
|
+
throw error;
|
|
44288
|
+
}
|
|
44289
|
+
}
|
|
44290
|
+
async function fetchEventsPage(params) {
|
|
44291
|
+
const url = buildUrl2("/events", {
|
|
44292
|
+
status: params.status ?? void 0,
|
|
44293
|
+
series_ticker: params.seriesTicker ?? void 0,
|
|
44294
|
+
limit: params.limit,
|
|
44295
|
+
cursor: params.cursor ?? void 0,
|
|
44296
|
+
with_nested_markets: params.withNested ? true : void 0
|
|
44297
|
+
});
|
|
44298
|
+
const data = await fetchKalshiJson(url, "GET /events");
|
|
44299
|
+
const record = asRecord2(data);
|
|
44300
|
+
const rows = asRecordArray2(record?.events, "GET /events").map(
|
|
44301
|
+
(raw) => toEventRow2(raw, params.withNested)
|
|
44302
|
+
);
|
|
44303
|
+
return { url, rows, nextCursor: readCursor(record) };
|
|
44304
|
+
}
|
|
44305
|
+
async function fetchEvent(eventTicker, withNested) {
|
|
44306
|
+
const url = buildUrl2(`/events/${encodeURIComponent(eventTicker)}`, {
|
|
44307
|
+
with_nested_markets: withNested ? true : void 0
|
|
44308
|
+
});
|
|
44309
|
+
try {
|
|
44310
|
+
const data = await fetchKalshiJson(url, "GET /events/{event_ticker}");
|
|
44311
|
+
const record = asRecord2(data);
|
|
44312
|
+
const event = asRecord2(record?.event);
|
|
44313
|
+
if (!event || typeof event.event_ticker !== "string") {
|
|
44314
|
+
throw new Error(
|
|
44315
|
+
"Kalshi GET /events/{event_ticker} returned an unexpected payload shape."
|
|
44316
|
+
);
|
|
44317
|
+
}
|
|
44318
|
+
return { url, event: toEventRow2(event, withNested, record?.markets) };
|
|
44319
|
+
} catch (error) {
|
|
44320
|
+
if (error instanceof KalshiNotFoundError) {
|
|
44321
|
+
invalid2(
|
|
44322
|
+
`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.`
|
|
44323
|
+
);
|
|
44324
|
+
}
|
|
44325
|
+
throw error;
|
|
44326
|
+
}
|
|
44327
|
+
}
|
|
44328
|
+
async function fetchOrderbook2(ticker, depth) {
|
|
44329
|
+
const url = buildUrl2(`/markets/${encodeURIComponent(ticker)}/orderbook`);
|
|
44330
|
+
try {
|
|
44331
|
+
const data = await fetchKalshiJson(
|
|
44332
|
+
url,
|
|
44333
|
+
"GET /markets/{ticker}/orderbook"
|
|
44334
|
+
);
|
|
44335
|
+
const record = asRecord2(data);
|
|
44336
|
+
if (!record || !asRecord2(record.orderbook_fp) && !asRecord2(record.orderbook)) {
|
|
44337
|
+
throw new Error(
|
|
44338
|
+
"Kalshi GET /markets/{ticker}/orderbook returned an unexpected payload shape."
|
|
44339
|
+
);
|
|
44340
|
+
}
|
|
44341
|
+
return { url, book: summarizeOrderbook2(record, ticker, depth) };
|
|
44342
|
+
} catch (error) {
|
|
44343
|
+
if (error instanceof KalshiNotFoundError) {
|
|
44344
|
+
invalid2(
|
|
44345
|
+
`no Kalshi market has ticker "${ticker}" (order book lookup returned 404). Find tickers with kalshi_search_markets.`
|
|
44346
|
+
);
|
|
44347
|
+
}
|
|
44348
|
+
throw error;
|
|
44349
|
+
}
|
|
44350
|
+
}
|
|
44351
|
+
async function fetchTrades(ticker, limit, cursor) {
|
|
44352
|
+
const url = buildUrl2("/markets/trades", {
|
|
44353
|
+
ticker,
|
|
44354
|
+
limit,
|
|
44355
|
+
cursor: cursor ?? void 0
|
|
44356
|
+
});
|
|
44357
|
+
const data = await fetchKalshiJson(url, "GET /markets/trades");
|
|
44358
|
+
const record = asRecord2(data);
|
|
44359
|
+
return {
|
|
44360
|
+
url,
|
|
44361
|
+
rows: asRecordArray2(record?.trades, "GET /markets/trades").map(toTradeRow),
|
|
44362
|
+
nextCursor: readCursor(record)
|
|
44363
|
+
};
|
|
44364
|
+
}
|
|
44365
|
+
async function assertMarketExists(ticker) {
|
|
44366
|
+
await fetchMarket(ticker);
|
|
44367
|
+
}
|
|
44368
|
+
async function fetchCandlesticks(seriesTicker, ticker, window) {
|
|
44369
|
+
const url = buildUrl2(
|
|
44370
|
+
`/series/${encodeURIComponent(seriesTicker)}/markets/${encodeURIComponent(ticker)}/candlesticks`,
|
|
44371
|
+
{
|
|
44372
|
+
start_ts: window.start_ts,
|
|
44373
|
+
end_ts: window.end_ts,
|
|
44374
|
+
period_interval: window.period_interval
|
|
44375
|
+
}
|
|
44376
|
+
);
|
|
44377
|
+
try {
|
|
44378
|
+
const data = await fetchKalshiJson(url, "GET candlesticks");
|
|
44379
|
+
const record = asRecord2(data);
|
|
44380
|
+
const candles = asRecordArray2(record?.candlesticks, "GET candlesticks").map(toCandle).filter((candle) => candle !== null).sort((a, b) => a.end_period_ts - b.end_period_ts);
|
|
44381
|
+
return { url, candles };
|
|
44382
|
+
} catch (error) {
|
|
44383
|
+
if (error instanceof KalshiNotFoundError) {
|
|
44384
|
+
invalid2(
|
|
44385
|
+
`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.`
|
|
44386
|
+
);
|
|
44387
|
+
}
|
|
44388
|
+
throw error;
|
|
44389
|
+
}
|
|
44390
|
+
}
|
|
44391
|
+
|
|
44392
|
+
// src/tools/kalshi/tools.ts
|
|
44393
|
+
var PROVIDER_NAME3 = "Kalshi";
|
|
44394
|
+
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.";
|
|
44395
|
+
var KALSHI_ATTRIBUTION = "Source: Kalshi public API";
|
|
44396
|
+
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).";
|
|
44397
|
+
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" }.`;
|
|
44398
|
+
function okResult2(sourceUrl, body) {
|
|
44399
|
+
const { returnedCount, may_have_more, ...rest } = body;
|
|
44400
|
+
return asTextContentResult({
|
|
44401
|
+
provider: PROVIDER_NAME3,
|
|
44402
|
+
runtime_status: "ok",
|
|
44403
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
44404
|
+
note: `${KALSHI_ODDS_NOTE} ${KALSHI_ATTRIBUTION} (${sourceUrl}).`,
|
|
44405
|
+
source_url: sourceUrl,
|
|
44406
|
+
...rest,
|
|
44407
|
+
returnedCount,
|
|
44408
|
+
may_have_more
|
|
44409
|
+
});
|
|
44410
|
+
}
|
|
44411
|
+
function errorResult4(tool101, error) {
|
|
44412
|
+
if (error instanceof KalshiInputError) {
|
|
44413
|
+
return asTextContentResult(
|
|
44414
|
+
{
|
|
44415
|
+
error: error.message,
|
|
44416
|
+
runtime_status: "invalid_input",
|
|
44417
|
+
provider: PROVIDER_NAME3,
|
|
44418
|
+
tool: tool101
|
|
44419
|
+
},
|
|
44420
|
+
true
|
|
44421
|
+
);
|
|
44422
|
+
}
|
|
44423
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44424
|
+
return asTextContentResult(
|
|
44425
|
+
{
|
|
44426
|
+
error: message,
|
|
44427
|
+
runtime_status: classifyRuntimeStatusFromError(message),
|
|
44428
|
+
provider: PROVIDER_NAME3,
|
|
44429
|
+
tool: tool101
|
|
44430
|
+
},
|
|
44431
|
+
true
|
|
44432
|
+
);
|
|
44433
|
+
}
|
|
44434
|
+
var TICKER_SCHEMA = {
|
|
44435
|
+
type: "string",
|
|
44436
|
+
description: 'Kalshi market ticker (uppercase, SERIES-EVENT-MARKET), e.g. "KXNIREFCALL-30JAN01-YES". Find tickers with kalshi_search_markets or kalshi_get_events.'
|
|
44437
|
+
};
|
|
44438
|
+
var SERIES_TICKER_SCHEMA = {
|
|
44439
|
+
type: "string",
|
|
44440
|
+
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.'
|
|
44441
|
+
};
|
|
44442
|
+
var CURSOR_SCHEMA2 = {
|
|
44443
|
+
type: "string",
|
|
44444
|
+
description: "Opaque next_cursor from a previous response; pass it to continue paging."
|
|
44445
|
+
};
|
|
44446
|
+
var LIMIT_SCHEMA2 = {
|
|
44447
|
+
type: "integer",
|
|
44448
|
+
minimum: 1,
|
|
44449
|
+
maximum: MAX_LIMIT4,
|
|
44450
|
+
description: `Maximum rows to return (1-${MAX_LIMIT4}, default ${DEFAULT_LIMIT4}).`
|
|
44451
|
+
};
|
|
44452
|
+
function getAllKalshiEndpoints() {
|
|
44453
|
+
return [
|
|
44454
|
+
{
|
|
44455
|
+
metadata: {
|
|
44456
|
+
resource: "kalshi-markets-search",
|
|
44457
|
+
operation: "read",
|
|
44458
|
+
tags: ["kalshi", "prediction_markets", "markets", "search", "odds"]
|
|
44459
|
+
},
|
|
44460
|
+
tool: {
|
|
44461
|
+
name: "kalshi_search_markets",
|
|
44462
|
+
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.
|
|
44463
|
+
|
|
44464
|
+
${PRICE_UNITS_DOC}
|
|
44465
|
+
|
|
44466
|
+
Args:
|
|
44467
|
+
- 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").
|
|
44468
|
+
- 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).
|
|
44469
|
+
- series_ticker (string, optional): restrict to one series, e.g. "KXBTCD".
|
|
44470
|
+
- event_ticker (string, optional): restrict to one event, e.g. "KXBTCD-26SEP1612".
|
|
44471
|
+
- limit (integer, optional): max rows (1-${MAX_LIMIT4}, default ${DEFAULT_LIMIT4}).
|
|
44472
|
+
- cursor (string, optional): next_cursor from a previous response to continue the scan.
|
|
44473
|
+
|
|
44474
|
+
Returns:
|
|
44475
|
+
${ENVELOPE_DOC2}
|
|
44476
|
+
"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" }].
|
|
44477
|
+
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.
|
|
44478
|
+
|
|
44479
|
+
Examples:
|
|
44480
|
+
- { "query": "btc", "series_ticker": "KXBTCD", "limit": 5 } \u2014 the live hourly BTC price brackets.
|
|
44481
|
+
- { "series_ticker": "KXFEDDECISION" } \u2014 every open Fed-decision market.
|
|
44482
|
+
- { "query": "referendum", "limit": 10 } \u2014 scan the newest open markets for referendum questions.
|
|
44483
|
+
- { "event_ticker": "KXBTCD-26SEP1612", "status": "all" } \u2014 every bracket of one event regardless of status.
|
|
44484
|
+
|
|
44485
|
+
Use when:
|
|
44486
|
+
- "What are the odds of X on Kalshi?" \u2014 find the market and read yes_bid / yes_ask / last_price
|
|
44487
|
+
- You need a ticker before an order-book, trades, or price-history read
|
|
44488
|
+
- You want every bracket of a series or event with current odds
|
|
44489
|
+
|
|
44490
|
+
Don't use when:
|
|
44491
|
+
- You already have a ticker (use kalshi_get_market)
|
|
44492
|
+
- You want the headline questions grouped by event (use kalshi_get_events)
|
|
44493
|
+
- You need Polymarket or Hyperliquid HIP-4 outcome markets (use their own tools)`,
|
|
44494
|
+
inputSchema: {
|
|
44495
|
+
type: "object",
|
|
44496
|
+
properties: {
|
|
44497
|
+
query: {
|
|
44498
|
+
type: "string",
|
|
44499
|
+
description: 'Free-text search terms; every term must appear in the ticker, event ticker, title, or yes/no subtitle (case-insensitive). Example: "btc".'
|
|
44500
|
+
},
|
|
44501
|
+
status: {
|
|
44502
|
+
type: "string",
|
|
44503
|
+
enum: [...MARKET_STATUS_FILTERS, ANY_STATUS],
|
|
44504
|
+
description: 'Listing status filter (default "open"). "all" returns every status.'
|
|
44505
|
+
},
|
|
44506
|
+
series_ticker: SERIES_TICKER_SCHEMA,
|
|
44507
|
+
event_ticker: {
|
|
44508
|
+
type: "string",
|
|
44509
|
+
description: 'Event ticker filter, e.g. "KXBTCD-26SEP1612" (one event, all of its markets).'
|
|
44510
|
+
},
|
|
44511
|
+
limit: LIMIT_SCHEMA2,
|
|
44512
|
+
cursor: CURSOR_SCHEMA2
|
|
44513
|
+
}
|
|
44514
|
+
},
|
|
44515
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44516
|
+
},
|
|
44517
|
+
handler: async (_client, args) => {
|
|
44518
|
+
const toolName = "kalshi_search_markets";
|
|
44519
|
+
try {
|
|
44520
|
+
const query = parseOptionalQuery2(args?.query);
|
|
44521
|
+
const status = parseStatusFilter(
|
|
44522
|
+
args?.status,
|
|
44523
|
+
MARKET_STATUS_FILTERS,
|
|
44524
|
+
"open"
|
|
44525
|
+
);
|
|
44526
|
+
const seriesTicker = parseOptionalTicker(
|
|
44527
|
+
args?.series_ticker,
|
|
44528
|
+
"series_ticker"
|
|
44529
|
+
);
|
|
44530
|
+
const eventTicker = parseOptionalTicker(
|
|
44531
|
+
args?.event_ticker,
|
|
44532
|
+
"event_ticker"
|
|
44533
|
+
);
|
|
44534
|
+
const limit = parseLimit2(args?.limit);
|
|
44535
|
+
const cursor = parseOptionalCursor(args?.cursor);
|
|
44536
|
+
const result2 = await searchMarkets({
|
|
44537
|
+
query,
|
|
44538
|
+
status,
|
|
44539
|
+
seriesTicker,
|
|
44540
|
+
eventTicker,
|
|
44541
|
+
limit,
|
|
44542
|
+
cursor
|
|
44543
|
+
});
|
|
44544
|
+
return okResult2(result2.url, {
|
|
44545
|
+
filters: {
|
|
44546
|
+
query,
|
|
44547
|
+
status: status ?? ANY_STATUS,
|
|
44548
|
+
series_ticker: seriesTicker,
|
|
44549
|
+
event_ticker: eventTicker,
|
|
44550
|
+
limit,
|
|
44551
|
+
cursor
|
|
44552
|
+
},
|
|
44553
|
+
scan: {
|
|
44554
|
+
page_size: result2.pageSize,
|
|
44555
|
+
pages_scanned: result2.pagesScanned,
|
|
44556
|
+
max_pages: query ? SEARCH_MAX_PAGES : 1,
|
|
44557
|
+
scanned: result2.scanned,
|
|
44558
|
+
matched: result2.matched
|
|
44559
|
+
},
|
|
44560
|
+
next_cursor: result2.nextCursor,
|
|
44561
|
+
rows: result2.rows,
|
|
44562
|
+
returnedCount: result2.rows.length,
|
|
44563
|
+
may_have_more: result2.mayHaveMore
|
|
44564
|
+
});
|
|
44565
|
+
} catch (error) {
|
|
44566
|
+
return errorResult4(toolName, error);
|
|
44567
|
+
}
|
|
44568
|
+
}
|
|
44569
|
+
},
|
|
44570
|
+
{
|
|
44571
|
+
metadata: {
|
|
44572
|
+
resource: "kalshi-events",
|
|
44573
|
+
operation: "read",
|
|
44574
|
+
tags: ["kalshi", "prediction_markets", "events", "odds"]
|
|
44575
|
+
},
|
|
44576
|
+
tool: {
|
|
44577
|
+
name: "kalshi_get_events",
|
|
44578
|
+
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.
|
|
44579
|
+
|
|
44580
|
+
${PRICE_UNITS_DOC}
|
|
44581
|
+
|
|
44582
|
+
Args:
|
|
44583
|
+
- event_ticker (string, optional): one event's ticker (exact), e.g. "KXBTCD-26SEP1612"; other filters are ignored.
|
|
44584
|
+
- status (string, optional): one of ${[...EVENT_STATUS_FILTERS, ANY_STATUS].map((item) => `"${item}"`).join(", ")} (default "open"; "all" = every status).
|
|
44585
|
+
- series_ticker (string, optional): restrict to one series, e.g. "KXFEDDECISION".
|
|
44586
|
+
- with_nested_markets (boolean, optional): include nested markets (default false). Slower for large pages.
|
|
44587
|
+
- limit (integer, optional): max events (1-${MAX_LIMIT4}, default ${DEFAULT_LIMIT4}).
|
|
44588
|
+
- cursor (string, optional): next_cursor from a previous response.
|
|
44589
|
+
|
|
44590
|
+
Returns:
|
|
44591
|
+
${ENVELOPE_DOC2}
|
|
44592
|
+
"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" }].
|
|
44593
|
+
|
|
44594
|
+
Examples:
|
|
44595
|
+
- { "series_ticker": "KXFEDDECISION", "with_nested_markets": true, "limit": 3 } \u2014 upcoming Fed meetings with their rate brackets.
|
|
44596
|
+
- { "event_ticker": "KXNIREFCALL-30JAN01", "with_nested_markets": true } \u2014 one event and its markets.
|
|
44597
|
+
- { "status": "open", "limit": 20 } \u2014 a page of open events across every category.
|
|
44598
|
+
|
|
44599
|
+
Use when:
|
|
44600
|
+
- "What is Kalshi pricing for the election / the Fed / a game?" \u2014 multi-market questions
|
|
44601
|
+
- You want to browse a series' upcoming events before picking a market
|
|
44602
|
+
- You need every bracket ticker of one event
|
|
44603
|
+
|
|
44604
|
+
Don't use when:
|
|
44605
|
+
- You want free-text search across markets (use kalshi_search_markets)
|
|
44606
|
+
- You need one market's full detail (use kalshi_get_market)`,
|
|
44607
|
+
inputSchema: {
|
|
44608
|
+
type: "object",
|
|
44609
|
+
properties: {
|
|
44610
|
+
event_ticker: {
|
|
44611
|
+
type: "string",
|
|
44612
|
+
description: 'Exact event ticker, e.g. "KXBTCD-26SEP1612". When set, other filters are ignored.'
|
|
44613
|
+
},
|
|
44614
|
+
status: {
|
|
44615
|
+
type: "string",
|
|
44616
|
+
enum: [...EVENT_STATUS_FILTERS, ANY_STATUS],
|
|
44617
|
+
description: 'Event status filter (default "open"). "all" returns every status.'
|
|
44618
|
+
},
|
|
44619
|
+
series_ticker: SERIES_TICKER_SCHEMA,
|
|
44620
|
+
with_nested_markets: {
|
|
44621
|
+
type: "boolean",
|
|
44622
|
+
description: `Include each event's markets (market_count plus up to ${EVENT_MARKETS_PREVIEW2} compact rows). Default false.`
|
|
44623
|
+
},
|
|
44624
|
+
limit: LIMIT_SCHEMA2,
|
|
44625
|
+
cursor: CURSOR_SCHEMA2
|
|
44626
|
+
}
|
|
44627
|
+
},
|
|
44628
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44629
|
+
},
|
|
44630
|
+
handler: async (_client, args) => {
|
|
44631
|
+
const toolName = "kalshi_get_events";
|
|
44632
|
+
try {
|
|
44633
|
+
const eventTicker = parseOptionalTicker(
|
|
44634
|
+
args?.event_ticker,
|
|
44635
|
+
"event_ticker"
|
|
44636
|
+
);
|
|
44637
|
+
const withNested = parseOptionalBoolean2(
|
|
44638
|
+
args?.with_nested_markets,
|
|
44639
|
+
"with_nested_markets",
|
|
44640
|
+
false
|
|
44641
|
+
);
|
|
44642
|
+
if (eventTicker) {
|
|
44643
|
+
const { url: url2, event } = await fetchEvent(eventTicker, withNested);
|
|
44644
|
+
return okResult2(url2, {
|
|
44645
|
+
filters: { event_ticker: eventTicker, with_nested_markets: withNested },
|
|
44646
|
+
next_cursor: null,
|
|
44647
|
+
rows: [event],
|
|
44648
|
+
returnedCount: 1,
|
|
44649
|
+
may_have_more: false
|
|
44650
|
+
});
|
|
44651
|
+
}
|
|
44652
|
+
const status = parseStatusFilter(
|
|
44653
|
+
args?.status,
|
|
44654
|
+
EVENT_STATUS_FILTERS,
|
|
44655
|
+
"open"
|
|
44656
|
+
);
|
|
44657
|
+
const seriesTicker = parseOptionalTicker(
|
|
44658
|
+
args?.series_ticker,
|
|
44659
|
+
"series_ticker"
|
|
44660
|
+
);
|
|
44661
|
+
const limit = parseLimit2(args?.limit);
|
|
44662
|
+
const cursor = parseOptionalCursor(args?.cursor);
|
|
44663
|
+
const { url, rows, nextCursor } = await fetchEventsPage({
|
|
44664
|
+
status,
|
|
44665
|
+
seriesTicker,
|
|
44666
|
+
limit,
|
|
44667
|
+
cursor,
|
|
44668
|
+
withNested
|
|
44669
|
+
});
|
|
44670
|
+
return okResult2(url, {
|
|
44671
|
+
filters: {
|
|
44672
|
+
status: status ?? ANY_STATUS,
|
|
44673
|
+
series_ticker: seriesTicker,
|
|
44674
|
+
with_nested_markets: withNested,
|
|
44675
|
+
limit,
|
|
44676
|
+
cursor
|
|
44677
|
+
},
|
|
44678
|
+
next_cursor: nextCursor,
|
|
44679
|
+
rows,
|
|
44680
|
+
returnedCount: rows.length,
|
|
44681
|
+
may_have_more: nextCursor !== null
|
|
44682
|
+
});
|
|
44683
|
+
} catch (error) {
|
|
44684
|
+
return errorResult4(toolName, error);
|
|
44685
|
+
}
|
|
44686
|
+
}
|
|
44687
|
+
},
|
|
44688
|
+
{
|
|
44689
|
+
metadata: {
|
|
44690
|
+
resource: "kalshi-market",
|
|
44691
|
+
operation: "read",
|
|
44692
|
+
tags: ["kalshi", "prediction_markets", "market", "odds"]
|
|
44693
|
+
},
|
|
44694
|
+
tool: {
|
|
44695
|
+
name: "kalshi_get_market",
|
|
44696
|
+
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).
|
|
44697
|
+
|
|
44698
|
+
${PRICE_UNITS_DOC}
|
|
44699
|
+
|
|
44700
|
+
Args:
|
|
44701
|
+
- ticker (string, required): market ticker, e.g. "KXNIREFCALL-30JAN01-YES".
|
|
44702
|
+
|
|
44703
|
+
Returns:
|
|
44704
|
+
${ENVELOPE_DOC2}
|
|
44705
|
+
"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" }.
|
|
44706
|
+
|
|
44707
|
+
Examples:
|
|
44708
|
+
- { "ticker": "KXNIREFCALL-30JAN01-YES" }
|
|
44709
|
+
- { "ticker": "KXBTCD-26SEP1612-T85799.99" }
|
|
44710
|
+
|
|
44711
|
+
Use when:
|
|
44712
|
+
- You have a ticker and need its full state, resolution rules, or close/expiration times
|
|
44713
|
+
- You are checking whether a market is still active before quoting its odds
|
|
44714
|
+
|
|
44715
|
+
Don't use when:
|
|
44716
|
+
- You only know the topic (use kalshi_search_markets)
|
|
44717
|
+
- You need bid/ask depth or the trade tape (use kalshi_get_orderbook / kalshi_get_trades)`,
|
|
44718
|
+
inputSchema: {
|
|
44719
|
+
type: "object",
|
|
44720
|
+
properties: { ticker: TICKER_SCHEMA },
|
|
44721
|
+
required: ["ticker"]
|
|
44722
|
+
},
|
|
44723
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44724
|
+
},
|
|
44725
|
+
handler: async (_client, args) => {
|
|
44726
|
+
const toolName = "kalshi_get_market";
|
|
44727
|
+
try {
|
|
44728
|
+
const ticker = parseRequiredTicker(args?.ticker);
|
|
44729
|
+
const { url, market } = await fetchMarket(ticker);
|
|
44730
|
+
return okResult2(url, {
|
|
44731
|
+
market,
|
|
44732
|
+
returnedCount: 1,
|
|
44733
|
+
may_have_more: false
|
|
44734
|
+
});
|
|
44735
|
+
} catch (error) {
|
|
44736
|
+
return errorResult4(toolName, error);
|
|
44737
|
+
}
|
|
44738
|
+
}
|
|
44739
|
+
},
|
|
44740
|
+
{
|
|
44741
|
+
metadata: {
|
|
44742
|
+
resource: "kalshi-orderbook",
|
|
44743
|
+
operation: "read",
|
|
44744
|
+
tags: ["kalshi", "prediction_markets", "orderbook", "liquidity", "odds"]
|
|
44745
|
+
},
|
|
44746
|
+
tool: {
|
|
44747
|
+
name: "kalshi_get_orderbook",
|
|
44748
|
+
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).
|
|
44749
|
+
|
|
44750
|
+
${PRICE_UNITS_DOC}
|
|
44751
|
+
|
|
44752
|
+
Args:
|
|
44753
|
+
- ticker (string, required): market ticker.
|
|
44754
|
+
- depth (integer, optional): levels per side (1-${MAX_BOOK_DEPTH2}, default ${DEFAULT_BOOK_DEPTH2}).
|
|
44755
|
+
|
|
44756
|
+
Returns:
|
|
44757
|
+
${ENVELOPE_DOC2}
|
|
44758
|
+
"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.
|
|
44759
|
+
|
|
44760
|
+
Examples:
|
|
44761
|
+
- { "ticker": "KXNIREFCALL-30JAN01-YES", "depth": 5 }
|
|
44762
|
+
|
|
44763
|
+
Use when:
|
|
44764
|
+
- You need executable odds (bid/ask) and depth, not just the last trade, or want to size a hypothetical position
|
|
44765
|
+
- You are checking spread and depth before trusting a thin market's implied probability
|
|
44766
|
+
|
|
44767
|
+
Don't use when:
|
|
44768
|
+
- You want the market's headline odds and metadata (use kalshi_get_market)
|
|
44769
|
+
- You want a CEX or Hyperliquid order book (use get_orderbook / hyperliquid_get_l2_book)`,
|
|
44770
|
+
inputSchema: {
|
|
44771
|
+
type: "object",
|
|
44772
|
+
properties: {
|
|
44773
|
+
ticker: TICKER_SCHEMA,
|
|
44774
|
+
depth: {
|
|
44775
|
+
type: "integer",
|
|
44776
|
+
minimum: 1,
|
|
44777
|
+
maximum: MAX_BOOK_DEPTH2,
|
|
44778
|
+
description: `Levels per side (1-${MAX_BOOK_DEPTH2}, default ${DEFAULT_BOOK_DEPTH2}).`
|
|
44779
|
+
}
|
|
44780
|
+
},
|
|
44781
|
+
required: ["ticker"]
|
|
44782
|
+
},
|
|
44783
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44784
|
+
},
|
|
44785
|
+
handler: async (_client, args) => {
|
|
44786
|
+
const toolName = "kalshi_get_orderbook";
|
|
44787
|
+
try {
|
|
44788
|
+
const ticker = parseRequiredTicker(args?.ticker);
|
|
44789
|
+
const depth = parseBookDepth2(args?.depth);
|
|
44790
|
+
const { url, book } = await fetchOrderbook2(ticker, depth);
|
|
44791
|
+
if (book.yes_levels_total === 0 && book.no_levels_total === 0) {
|
|
44792
|
+
await assertMarketExists(ticker);
|
|
44793
|
+
}
|
|
44794
|
+
return okResult2(url, {
|
|
44795
|
+
...book,
|
|
44796
|
+
depth,
|
|
44797
|
+
returnedCount: book.yes_bids.length + book.no_bids.length,
|
|
44798
|
+
may_have_more: book.yes_levels_total > depth || book.no_levels_total > depth
|
|
44799
|
+
});
|
|
44800
|
+
} catch (error) {
|
|
44801
|
+
return errorResult4(toolName, error);
|
|
44802
|
+
}
|
|
44803
|
+
}
|
|
44804
|
+
},
|
|
44805
|
+
{
|
|
44806
|
+
metadata: {
|
|
44807
|
+
resource: "kalshi-trades",
|
|
44808
|
+
operation: "read",
|
|
44809
|
+
tags: ["kalshi", "prediction_markets", "trades", "tape", "odds"]
|
|
44810
|
+
},
|
|
44811
|
+
tool: {
|
|
44812
|
+
name: "kalshi_get_trades",
|
|
44813
|
+
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.
|
|
44814
|
+
|
|
44815
|
+
${PRICE_UNITS_DOC}
|
|
44816
|
+
|
|
44817
|
+
Args:
|
|
44818
|
+
- ticker (string, required): market ticker.
|
|
44819
|
+
- limit (integer, optional): max trades (1-${MAX_TRADES_LIMIT}, default ${DEFAULT_TRADES_LIMIT}).
|
|
44820
|
+
- cursor (string, optional): next_cursor from a previous response for older trades.
|
|
44821
|
+
|
|
44822
|
+
Returns:
|
|
44823
|
+
${ENVELOPE_DOC2}
|
|
44824
|
+
"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.
|
|
44825
|
+
|
|
44826
|
+
Examples:
|
|
44827
|
+
- { "ticker": "KXNIREFCALL-30JAN01-YES", "limit": 20 }
|
|
44828
|
+
- { "ticker": "KXNIREFCALL-30JAN01-YES", "limit": 100, "cursor": "<next_cursor>" } \u2014 the next page of older fills.
|
|
44829
|
+
|
|
44830
|
+
Use when:
|
|
44831
|
+
- "Who is hitting the bid / lifting the offer on this market?" \u2014 recent flow and taker direction
|
|
44832
|
+
- You want fill prices and sizes rather than resting quotes
|
|
44833
|
+
|
|
44834
|
+
Don't use when:
|
|
44835
|
+
- You need resting liquidity (use kalshi_get_orderbook)
|
|
44836
|
+
- You need an OHLC series over time (use kalshi_get_price_history)`,
|
|
44837
|
+
inputSchema: {
|
|
44838
|
+
type: "object",
|
|
44839
|
+
properties: {
|
|
44840
|
+
ticker: TICKER_SCHEMA,
|
|
44841
|
+
limit: {
|
|
44842
|
+
type: "integer",
|
|
44843
|
+
minimum: 1,
|
|
44844
|
+
maximum: MAX_TRADES_LIMIT,
|
|
44845
|
+
description: `Maximum trades to return (1-${MAX_TRADES_LIMIT}, default ${DEFAULT_TRADES_LIMIT}).`
|
|
44846
|
+
},
|
|
44847
|
+
cursor: CURSOR_SCHEMA2
|
|
44848
|
+
},
|
|
44849
|
+
required: ["ticker"]
|
|
44850
|
+
},
|
|
44851
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44852
|
+
},
|
|
44853
|
+
handler: async (_client, args) => {
|
|
44854
|
+
const toolName = "kalshi_get_trades";
|
|
44855
|
+
try {
|
|
44856
|
+
const ticker = parseRequiredTicker(args?.ticker);
|
|
44857
|
+
const limit = parseTradesLimit(args?.limit);
|
|
44858
|
+
const cursor = parseOptionalCursor(args?.cursor);
|
|
44859
|
+
const { url, rows, nextCursor } = await fetchTrades(
|
|
44860
|
+
ticker,
|
|
44861
|
+
limit,
|
|
44862
|
+
cursor
|
|
44863
|
+
);
|
|
44864
|
+
if (rows.length === 0 && cursor === null) {
|
|
44865
|
+
await assertMarketExists(ticker);
|
|
44866
|
+
}
|
|
44867
|
+
return okResult2(url, {
|
|
44868
|
+
ticker,
|
|
44869
|
+
filters: { limit, cursor },
|
|
44870
|
+
next_cursor: nextCursor,
|
|
44871
|
+
rows,
|
|
44872
|
+
returnedCount: rows.length,
|
|
44873
|
+
may_have_more: nextCursor !== null
|
|
44874
|
+
});
|
|
44875
|
+
} catch (error) {
|
|
44876
|
+
return errorResult4(toolName, error);
|
|
44877
|
+
}
|
|
44878
|
+
}
|
|
44879
|
+
},
|
|
44880
|
+
{
|
|
44881
|
+
metadata: {
|
|
44882
|
+
resource: "kalshi-price-history",
|
|
44883
|
+
operation: "read",
|
|
44884
|
+
tags: [
|
|
44885
|
+
"kalshi",
|
|
44886
|
+
"prediction_markets",
|
|
44887
|
+
"price-history",
|
|
44888
|
+
"candlesticks",
|
|
44889
|
+
"odds",
|
|
44890
|
+
"timeseries"
|
|
44891
|
+
]
|
|
44892
|
+
},
|
|
44893
|
+
tool: {
|
|
44894
|
+
name: "kalshi_get_price_history",
|
|
44895
|
+
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.
|
|
44896
|
+
|
|
44897
|
+
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".
|
|
44898
|
+
|
|
44899
|
+
Args:
|
|
44900
|
+
- ticker (string, required): market ticker.
|
|
44901
|
+
- period_interval (integer, optional): minutes per candle, one of ${PERIOD_INTERVALS.join(", ")} (default ${DEFAULT_PERIOD_INTERVAL}).
|
|
44902
|
+
- 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.
|
|
44903
|
+
- end (string | integer, optional): ISO 8601 datetime or unix seconds (default now).
|
|
44904
|
+
- 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.
|
|
44905
|
+
|
|
44906
|
+
Returns:
|
|
44907
|
+
${ENVELOPE_DOC2}
|
|
44908
|
+
"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.
|
|
44909
|
+
|
|
44910
|
+
Examples:
|
|
44911
|
+
- { "ticker": "KXNIREFCALL-30JAN01-YES", "period_interval": 1440 } \u2014 one year of daily candles.
|
|
44912
|
+
- { "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.
|
|
44913
|
+
|
|
44914
|
+
Use when:
|
|
44915
|
+
- "How have the odds of X moved this week/month?" \u2014 trend, drawdown, or repricing questions
|
|
44916
|
+
- You want to time-align a market's odds with news or on-chain events
|
|
44917
|
+
|
|
44918
|
+
Don't use when:
|
|
44919
|
+
- You need the current bid/ask depth (use kalshi_get_orderbook)
|
|
44920
|
+
- You need crypto token price history (use get_coin_market_chart_range or CCXT OHLCV tools)`,
|
|
44921
|
+
inputSchema: {
|
|
44922
|
+
type: "object",
|
|
44923
|
+
properties: {
|
|
44924
|
+
ticker: TICKER_SCHEMA,
|
|
44925
|
+
period_interval: {
|
|
44926
|
+
type: "integer",
|
|
44927
|
+
enum: [...PERIOD_INTERVALS],
|
|
44928
|
+
description: `Minutes per candle: 1, 60, or 1440 (default ${DEFAULT_PERIOD_INTERVAL}).`
|
|
44929
|
+
},
|
|
44930
|
+
start: {
|
|
44931
|
+
type: "string",
|
|
44932
|
+
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.'
|
|
44933
|
+
},
|
|
44934
|
+
end: {
|
|
44935
|
+
type: "string",
|
|
44936
|
+
description: "Window end as ISO 8601 or unix seconds (default now). Pass an earlier end to page back in time."
|
|
44937
|
+
},
|
|
44938
|
+
series_ticker: {
|
|
44939
|
+
type: "string",
|
|
44940
|
+
description: `Series ticker override for the candlestick route. Defaults to the ticker's leading segment (e.g. "KXBTCD").`
|
|
44941
|
+
}
|
|
44942
|
+
},
|
|
44943
|
+
required: ["ticker"]
|
|
44944
|
+
},
|
|
44945
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
44946
|
+
},
|
|
44947
|
+
handler: async (_client, args) => {
|
|
44948
|
+
const toolName = "kalshi_get_price_history";
|
|
44949
|
+
try {
|
|
44950
|
+
const ticker = parseRequiredTicker(args?.ticker);
|
|
44951
|
+
const explicitSeries = parseOptionalTicker(
|
|
44952
|
+
args?.series_ticker,
|
|
44953
|
+
"series_ticker"
|
|
44954
|
+
);
|
|
44955
|
+
const window = resolveCandleWindow(args);
|
|
44956
|
+
const seriesTicker = explicitSeries ?? seriesTickerFromTicker(ticker);
|
|
44957
|
+
const { url, candles } = await fetchCandlesticks(
|
|
44958
|
+
seriesTicker,
|
|
44959
|
+
ticker,
|
|
44960
|
+
window
|
|
44961
|
+
);
|
|
44962
|
+
const returned = candles.slice(-MAX_CANDLES);
|
|
44963
|
+
const clamped = window.window_truncated || returned.length < candles.length;
|
|
44964
|
+
return okResult2(url, {
|
|
44965
|
+
ticker,
|
|
44966
|
+
series_ticker: seriesTicker,
|
|
44967
|
+
series_ticker_source: explicitSeries ? "argument" : "derived_from_ticker",
|
|
44968
|
+
period_interval: window.period_interval,
|
|
44969
|
+
window: {
|
|
44970
|
+
start: new Date(window.start_ts * 1e3).toISOString(),
|
|
44971
|
+
end: new Date(window.end_ts * 1e3).toISOString(),
|
|
44972
|
+
requested_start: window.requested_start_ts !== null ? new Date(window.requested_start_ts * 1e3).toISOString() : null,
|
|
44973
|
+
truncated: clamped,
|
|
44974
|
+
max_candles: MAX_CANDLES
|
|
44975
|
+
},
|
|
44976
|
+
summary: summarizeCandles(returned),
|
|
44977
|
+
candles_total: candles.length,
|
|
44978
|
+
candles: returned,
|
|
44979
|
+
returnedCount: returned.length,
|
|
44980
|
+
may_have_more: clamped
|
|
44981
|
+
});
|
|
44982
|
+
} catch (error) {
|
|
44983
|
+
return errorResult4(toolName, error);
|
|
44984
|
+
}
|
|
44985
|
+
}
|
|
44986
|
+
}
|
|
44987
|
+
];
|
|
44988
|
+
}
|
|
44989
|
+
|
|
43831
44990
|
// src/tools/signals/http.ts
|
|
43832
44991
|
var SIGNALS_FETCH_TIMEOUT_MS = 1e4;
|
|
43833
44992
|
var SignalsInputError = class extends Error {
|
|
@@ -43886,13 +45045,13 @@ async function fetchJsonWithTimeout(url, label, timeoutMs = SIGNALS_FETCH_TIMEOU
|
|
|
43886
45045
|
clearTimeout(timer);
|
|
43887
45046
|
}
|
|
43888
45047
|
}
|
|
43889
|
-
function
|
|
45048
|
+
function parseFiniteNumber4(value) {
|
|
43890
45049
|
const num = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
43891
45050
|
return Number.isFinite(num) ? num : null;
|
|
43892
45051
|
}
|
|
43893
45052
|
function parseIntegerArg(value, name, min, max, fallback) {
|
|
43894
45053
|
if (value === void 0 || value === null) return fallback;
|
|
43895
|
-
const parsed =
|
|
45054
|
+
const parsed = parseFiniteNumber4(value);
|
|
43896
45055
|
if (parsed === null || !Number.isInteger(parsed) || parsed < min || parsed > max) {
|
|
43897
45056
|
throw new SignalsInputError(
|
|
43898
45057
|
`${name} must be an integer between ${min} and ${max}; got ${JSON.stringify(value)}.`
|
|
@@ -43902,7 +45061,7 @@ function parseIntegerArg(value, name, min, max, fallback) {
|
|
|
43902
45061
|
}
|
|
43903
45062
|
function parseNumberArg(value, name, min, max, fallback) {
|
|
43904
45063
|
if (value === void 0 || value === null) return fallback;
|
|
43905
|
-
const parsed =
|
|
45064
|
+
const parsed = parseFiniteNumber4(value);
|
|
43906
45065
|
if (parsed === null || parsed < min || parsed > max) {
|
|
43907
45066
|
throw new SignalsInputError(
|
|
43908
45067
|
`${name} must be a number between ${min} and ${max}; got ${JSON.stringify(value)}.`
|
|
@@ -44000,8 +45159,8 @@ function normalizeFearGreed(raw) {
|
|
|
44000
45159
|
let newestSeen = -Infinity;
|
|
44001
45160
|
for (const entry2 of body.data) {
|
|
44002
45161
|
const item = entry2 ?? {};
|
|
44003
|
-
const value =
|
|
44004
|
-
const unixTimestamp =
|
|
45162
|
+
const value = parseFiniteNumber4(item.value);
|
|
45163
|
+
const unixTimestamp = parseFiniteNumber4(item.timestamp);
|
|
44005
45164
|
if (value === null || unixTimestamp === null) continue;
|
|
44006
45165
|
history.push({
|
|
44007
45166
|
value,
|
|
@@ -44011,7 +45170,7 @@ function normalizeFearGreed(raw) {
|
|
|
44011
45170
|
});
|
|
44012
45171
|
if (unixTimestamp > newestSeen) {
|
|
44013
45172
|
newestSeen = unixTimestamp;
|
|
44014
|
-
timeUntilUpdate =
|
|
45173
|
+
timeUntilUpdate = parseFiniteNumber4(item.time_until_update);
|
|
44015
45174
|
}
|
|
44016
45175
|
}
|
|
44017
45176
|
history.sort((a, b) => b.unix_timestamp - a.unix_timestamp);
|
|
@@ -44214,7 +45373,7 @@ var COIN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,99}$/;
|
|
|
44214
45373
|
var VS_CURRENCY_RE = /^[a-z0-9]{2,10}$/;
|
|
44215
45374
|
function parseDays(value) {
|
|
44216
45375
|
if (value === void 0 || value === null) return DEFAULT_DAYS;
|
|
44217
|
-
const parsed =
|
|
45376
|
+
const parsed = parseFiniteNumber4(value);
|
|
44218
45377
|
const match = OHLC_DAYS.find((days) => days === parsed);
|
|
44219
45378
|
if (match === void 0) {
|
|
44220
45379
|
throw new SignalsInputError(
|
|
@@ -44322,7 +45481,7 @@ function parseCandles(raw) {
|
|
|
44322
45481
|
const byTime = /* @__PURE__ */ new Map();
|
|
44323
45482
|
for (const entry2 of raw) {
|
|
44324
45483
|
if (!Array.isArray(entry2) || entry2.length < 5) continue;
|
|
44325
|
-
const [time, open, high, low, close] = entry2.slice(0, 5).map(
|
|
45484
|
+
const [time, open, high, low, close] = entry2.slice(0, 5).map(parseFiniteNumber4);
|
|
44326
45485
|
if (time === null || open === null || high === null || low === null || close === null) {
|
|
44327
45486
|
continue;
|
|
44328
45487
|
}
|
|
@@ -44572,8 +45731,8 @@ function parseChart(raw, chart) {
|
|
|
44572
45731
|
const points = [];
|
|
44573
45732
|
for (const entry2 of body.values) {
|
|
44574
45733
|
const point = entry2 ?? {};
|
|
44575
|
-
const x =
|
|
44576
|
-
const y =
|
|
45734
|
+
const x = parseFiniteNumber4(point.x);
|
|
45735
|
+
const y = parseFiniteNumber4(point.y);
|
|
44577
45736
|
if (x === null || y === null) continue;
|
|
44578
45737
|
points.push({ day: new Date(x * 1e3).toISOString().slice(0, 10), value: y });
|
|
44579
45738
|
}
|
|
@@ -44665,11 +45824,11 @@ function parseHacksArgs(args) {
|
|
|
44665
45824
|
sinceMs
|
|
44666
45825
|
};
|
|
44667
45826
|
}
|
|
44668
|
-
function
|
|
45827
|
+
function optionalString3(value) {
|
|
44669
45828
|
return typeof value === "string" && value.trim() !== "" ? value : null;
|
|
44670
45829
|
}
|
|
44671
45830
|
function idString(value) {
|
|
44672
|
-
if (typeof value === "string") return
|
|
45831
|
+
if (typeof value === "string") return optionalString3(value);
|
|
44673
45832
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
44674
45833
|
return null;
|
|
44675
45834
|
}
|
|
@@ -44683,25 +45842,25 @@ function normalizeHackRows(raw) {
|
|
|
44683
45842
|
for (const entry2 of raw) {
|
|
44684
45843
|
if (!entry2 || typeof entry2 !== "object") continue;
|
|
44685
45844
|
const item = entry2;
|
|
44686
|
-
const timestamp =
|
|
44687
|
-
const name =
|
|
45845
|
+
const timestamp = parseFiniteNumber4(item.date);
|
|
45846
|
+
const name = optionalString3(item.name);
|
|
44688
45847
|
if (timestamp === null || name === null) continue;
|
|
44689
45848
|
const chains = Array.isArray(item.chain) ? item.chain.filter((chain) => typeof chain === "string") : typeof item.chain === "string" ? [item.chain] : [];
|
|
44690
45849
|
rows.push({
|
|
44691
45850
|
date: new Date(timestamp * 1e3).toISOString(),
|
|
44692
45851
|
timestamp,
|
|
44693
45852
|
name,
|
|
44694
|
-
classification:
|
|
44695
|
-
technique:
|
|
44696
|
-
amount_usd:
|
|
45853
|
+
classification: optionalString3(item.classification),
|
|
45854
|
+
technique: optionalString3(item.technique),
|
|
45855
|
+
amount_usd: parseFiniteNumber4(item.amount),
|
|
44697
45856
|
chains,
|
|
44698
45857
|
bridge_hack: item.bridgeHack === true,
|
|
44699
|
-
target_type:
|
|
44700
|
-
source:
|
|
44701
|
-
returned_funds_usd:
|
|
45858
|
+
target_type: optionalString3(item.targetType),
|
|
45859
|
+
source: optionalString3(item.source),
|
|
45860
|
+
returned_funds_usd: parseFiniteNumber4(item.returnedFunds),
|
|
44702
45861
|
defillama_id: idString(item.defillamaId),
|
|
44703
45862
|
parent_protocol_id: idString(item.parentProtocolId),
|
|
44704
|
-
language:
|
|
45863
|
+
language: optionalString3(item.language)
|
|
44705
45864
|
});
|
|
44706
45865
|
}
|
|
44707
45866
|
rows.sort(
|
|
@@ -44722,7 +45881,7 @@ async function fetchHackRows() {
|
|
|
44722
45881
|
}
|
|
44723
45882
|
|
|
44724
45883
|
// src/tools/signals/tools.ts
|
|
44725
|
-
function
|
|
45884
|
+
function errorResult5(tool101, error, extra = {}) {
|
|
44726
45885
|
if (error instanceof SignalsInputError) {
|
|
44727
45886
|
return asTextContentResult(
|
|
44728
45887
|
{ error: error.message, runtime_status: "invalid_input", tool: tool101, ...extra },
|
|
@@ -44800,7 +45959,7 @@ Don't use when:
|
|
|
44800
45959
|
may_have_more: history.length >= limit
|
|
44801
45960
|
});
|
|
44802
45961
|
} catch (error) {
|
|
44803
|
-
return
|
|
45962
|
+
return errorResult5(toolName, error);
|
|
44804
45963
|
}
|
|
44805
45964
|
}
|
|
44806
45965
|
},
|
|
@@ -44919,7 +46078,7 @@ Don't use when:
|
|
|
44919
46078
|
...computeTechnicalIndicators(candles, parsed)
|
|
44920
46079
|
});
|
|
44921
46080
|
} catch (error) {
|
|
44922
|
-
return
|
|
46081
|
+
return errorResult5(toolName, error);
|
|
44923
46082
|
}
|
|
44924
46083
|
}
|
|
44925
46084
|
},
|
|
@@ -45016,7 +46175,7 @@ Don't use when:
|
|
|
45016
46175
|
rows
|
|
45017
46176
|
});
|
|
45018
46177
|
} catch (error) {
|
|
45019
|
-
return
|
|
46178
|
+
return errorResult5(toolName, error);
|
|
45020
46179
|
}
|
|
45021
46180
|
}
|
|
45022
46181
|
},
|
|
@@ -45099,7 +46258,7 @@ Don't use when:
|
|
|
45099
46258
|
rows: page
|
|
45100
46259
|
});
|
|
45101
46260
|
} catch (error) {
|
|
45102
|
-
return
|
|
46261
|
+
return errorResult5(toolName, error);
|
|
45103
46262
|
}
|
|
45104
46263
|
}
|
|
45105
46264
|
}
|
|
@@ -45239,7 +46398,7 @@ function blockscoutGet(chain, path) {
|
|
|
45239
46398
|
}
|
|
45240
46399
|
var ownCoinGecko = null;
|
|
45241
46400
|
async function resolvePrice(chain, address, token) {
|
|
45242
|
-
const blockscoutRate =
|
|
46401
|
+
const blockscoutRate = parseFiniteNumber4(token.exchange_rate);
|
|
45243
46402
|
if (blockscoutRate !== null && blockscoutRate > 0) {
|
|
45244
46403
|
return { price: blockscoutRate, source: "blockscout_exchange_rate" };
|
|
45245
46404
|
}
|
|
@@ -45251,7 +46410,7 @@ async function resolvePrice(chain, address, token) {
|
|
|
45251
46410
|
{ network: chain.gecko }
|
|
45252
46411
|
);
|
|
45253
46412
|
const prices = result2?.data?.attributes?.token_prices ?? {};
|
|
45254
|
-
const price =
|
|
46413
|
+
const price = parseFiniteNumber4(
|
|
45255
46414
|
prices[address.toLowerCase()] ?? prices[address]
|
|
45256
46415
|
);
|
|
45257
46416
|
if (price !== null && price > 0) {
|
|
@@ -45270,11 +46429,11 @@ async function getTokenHolderDistribution(args, now = Date.now) {
|
|
|
45270
46429
|
`this address is a ${String(token.type)} token; holder tiers are computed for ERC-20 tokens only.`
|
|
45271
46430
|
);
|
|
45272
46431
|
}
|
|
45273
|
-
const decimals =
|
|
46432
|
+
const decimals = parseFiniteNumber4(token.decimals);
|
|
45274
46433
|
if (decimals === null) {
|
|
45275
46434
|
throw new Error("Blockscout returned no decimals for this token");
|
|
45276
46435
|
}
|
|
45277
|
-
const totalHolders =
|
|
46436
|
+
const totalHolders = parseFiniteNumber4(token.holders_count ?? token.holders);
|
|
45278
46437
|
const { price, source: priceSource } = priceOverride !== null ? { price: priceOverride, source: "caller" } : await resolvePrice(chain, address, token);
|
|
45279
46438
|
const base = {
|
|
45280
46439
|
provider: BLOCKSCOUT_PROVIDER,
|
|
@@ -45322,7 +46481,7 @@ async function getTokenHolderDistribution(args, now = Date.now) {
|
|
|
45322
46481
|
let belowFloor = false;
|
|
45323
46482
|
for (const item of items) {
|
|
45324
46483
|
const holder = item ?? {};
|
|
45325
|
-
const units =
|
|
46484
|
+
const units = parseFiniteNumber4(holder.value);
|
|
45326
46485
|
if (units === null) continue;
|
|
45327
46486
|
const usd2 = units / 10 ** decimals * price;
|
|
45328
46487
|
rows.push({ usd: usd2, is_contract: holder.address?.is_contract === true });
|
|
@@ -46390,7 +47549,7 @@ function calculateBackoffDelay2(attempt, baseDelay) {
|
|
|
46390
47549
|
const jitter = Math.random() * 500;
|
|
46391
47550
|
return exponentialDelay + jitter;
|
|
46392
47551
|
}
|
|
46393
|
-
function
|
|
47552
|
+
function sleep3(ms) {
|
|
46394
47553
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
46395
47554
|
}
|
|
46396
47555
|
async function fetchWithTimeout(url, options, timeout) {
|
|
@@ -46456,7 +47615,7 @@ async function resilientFetch(url, fetchOptions = {}, resilientOptions = {}) {
|
|
|
46456
47615
|
delayMs: Math.round(delay2),
|
|
46457
47616
|
error: lastError.message
|
|
46458
47617
|
});
|
|
46459
|
-
await
|
|
47618
|
+
await sleep3(delay2);
|
|
46460
47619
|
continue;
|
|
46461
47620
|
}
|
|
46462
47621
|
}
|
|
@@ -46652,7 +47811,7 @@ async function assertHostResolvesPublic(host) {
|
|
|
46652
47811
|
}
|
|
46653
47812
|
}
|
|
46654
47813
|
}
|
|
46655
|
-
function
|
|
47814
|
+
function buildUrl3(entry2, path, query) {
|
|
46656
47815
|
if (/^[a-z]+:\/\//i.test(path) || path.includes("@")) {
|
|
46657
47816
|
throw new FetchInputError(
|
|
46658
47817
|
`Invalid argument: path "${path}" must be a path on the host, not a full URL.`
|
|
@@ -46685,7 +47844,7 @@ async function fetchPublicApi(params) {
|
|
|
46685
47844
|
`Method "${method}" is not supported for host "${entry2.host}" (allowed: ${entry2.methods.join(", ")}).`
|
|
46686
47845
|
);
|
|
46687
47846
|
}
|
|
46688
|
-
const url =
|
|
47847
|
+
const url = buildUrl3(entry2, params.path, params.query);
|
|
46689
47848
|
await assertHostResolvesPublic(entry2.host);
|
|
46690
47849
|
const headers = { Accept: "application/json" };
|
|
46691
47850
|
let payload;
|
|
@@ -46743,7 +47902,7 @@ async function fetchPublicApi(params) {
|
|
|
46743
47902
|
}
|
|
46744
47903
|
|
|
46745
47904
|
// src/tools/fetch/tools.ts
|
|
46746
|
-
function
|
|
47905
|
+
function asRecord3(value) {
|
|
46747
47906
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
46748
47907
|
}
|
|
46749
47908
|
function getAllFetchEndpoints() {
|
|
@@ -46814,7 +47973,7 @@ Don't use when:
|
|
|
46814
47973
|
host: String(a.host ?? ""),
|
|
46815
47974
|
path: String(a.path ?? ""),
|
|
46816
47975
|
method: a.method === "POST" ? "POST" : "GET",
|
|
46817
|
-
query:
|
|
47976
|
+
query: asRecord3(a.query),
|
|
46818
47977
|
body: a.body
|
|
46819
47978
|
});
|
|
46820
47979
|
return asTextContentResult(data);
|
|
@@ -48085,7 +49244,7 @@ var PROVIDER_CATALOG = [
|
|
|
48085
49244
|
id: "rwa",
|
|
48086
49245
|
name: "RWA Perps",
|
|
48087
49246
|
prefix: "rwa_",
|
|
48088
|
-
type: "Tokenized real-world-asset perps: cross-venue funding, markets, and open interest across Hyperliquid HIP-3 builder dexes,
|
|
49247
|
+
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
49248
|
clientStrategy: "internal",
|
|
48090
49249
|
alwaysAvailable: true,
|
|
48091
49250
|
matches: (toolName, metadata103) => matchesProvider("rwa", toolName, metadata103)
|
|
@@ -48109,11 +49268,8 @@ var PROVIDER_CATALOG = [
|
|
|
48109
49268
|
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
49269
|
clientStrategy: "internal",
|
|
48111
49270
|
credentialRequirement: {
|
|
48112
|
-
label: "
|
|
48113
|
-
isConfigured: () => hasAllEnvVars(
|
|
48114
|
-
"SHARPE_ARCHIVE_SUPABASE_URL",
|
|
48115
|
-
"SHARPE_ARCHIVE_SUPABASE_SERVICE_ROLE_KEY"
|
|
48116
|
-
)
|
|
49271
|
+
label: "CLICKHOUSE_URL and CLICKHOUSE_READ_PASSWORD",
|
|
49272
|
+
isConfigured: () => hasAllEnvVars("CLICKHOUSE_URL", "CLICKHOUSE_READ_PASSWORD")
|
|
48117
49273
|
},
|
|
48118
49274
|
matches: (toolName, metadata103) => matchesProvider("archive", toolName, metadata103)
|
|
48119
49275
|
},
|
|
@@ -48126,6 +49282,15 @@ var PROVIDER_CATALOG = [
|
|
|
48126
49282
|
alwaysAvailable: true,
|
|
48127
49283
|
matches: (toolName, metadata103) => matchesProvider("polymarket", toolName, metadata103)
|
|
48128
49284
|
},
|
|
49285
|
+
{
|
|
49286
|
+
id: "kalshi",
|
|
49287
|
+
name: "Kalshi",
|
|
49288
|
+
prefix: "kalshi_",
|
|
49289
|
+
type: "Kalshi prediction markets: market and event search, market-implied odds, order books, recent trades and candlestick price history (keyless public Trade API v2)",
|
|
49290
|
+
clientStrategy: "internal",
|
|
49291
|
+
alwaysAvailable: true,
|
|
49292
|
+
matches: (toolName, metadata103) => matchesProvider("kalshi", toolName, metadata103)
|
|
49293
|
+
},
|
|
48129
49294
|
{
|
|
48130
49295
|
id: "alternative",
|
|
48131
49296
|
name: "Alternative.me",
|
|
@@ -48392,7 +49557,7 @@ var RETIRED_TOOL_REPLACEMENTS = {
|
|
|
48392
49557
|
get_community_notes: "No direct replacement; use get_categories_market_data or get_categories for category data.",
|
|
48393
49558
|
get_event_labels: "No direct replacement; use get_categories_market_data or get_categories for category data."
|
|
48394
49559
|
};
|
|
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
|
|
49560
|
+
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
49561
|
var UNSERVABLE_TOOLS_DATE = "2026-09-24";
|
|
48397
49562
|
var COINGECKO_ENTERPRISE = "because it needs a CoinGecko Enterprise plan, which Hive does not have";
|
|
48398
49563
|
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 +49589,7 @@ var MORALIS_WALLET_CHAINS = "Use get_wallet_balances across networks, or alchemy
|
|
|
48424
49589
|
var CCXT_PRIVATE = "because every exchange that serves it requires an authenticated exchange account";
|
|
48425
49590
|
var CCXT_BORROW_REPLACEMENT = "Use ccxt_fetch_borrow_rate_history or ccxt_fetch_borrow_rate_histories (public) and read the latest point.";
|
|
48426
49591
|
var CCXT_WRONG_DATA_DATE = "2026-10-22";
|
|
49592
|
+
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
49593
|
var UNSERVABLE_TOOLS = {
|
|
48428
49594
|
get_coin_circulating_supply_chart: {
|
|
48429
49595
|
reason: COINGECKO_ENTERPRISE,
|
|
@@ -48801,7 +49967,18 @@ var UNSERVABLE_TOOLS = {
|
|
|
48801
49967
|
"Use alchemy_get_block_number for the latest block on an EVM network."
|
|
48802
49968
|
),
|
|
48803
49969
|
moralis_resolve_ens_domain: moralisRetired(MORALIS_ENS_NONE),
|
|
48804
|
-
moralis_resolve_address: moralisRetired(MORALIS_ENS_NONE)
|
|
49970
|
+
moralis_resolve_address: moralisRetired(MORALIS_ENS_NONE),
|
|
49971
|
+
// Removed venues (founder, 2026-10-26): the venue clients are deleted.
|
|
49972
|
+
rwa_get_ostium_markets: {
|
|
49973
|
+
reason: "because Hive no longer serves the Ostium venue",
|
|
49974
|
+
replacement: RWA_REMAINING_VENUES,
|
|
49975
|
+
date: "2026-10-26"
|
|
49976
|
+
},
|
|
49977
|
+
rwa_get_avantis_markets: {
|
|
49978
|
+
reason: "because Hive no longer serves the Avantis venue",
|
|
49979
|
+
replacement: RWA_REMAINING_VENUES,
|
|
49980
|
+
date: "2026-10-26"
|
|
49981
|
+
}
|
|
48805
49982
|
};
|
|
48806
49983
|
function isUnservableTool(toolName) {
|
|
48807
49984
|
return Object.prototype.hasOwnProperty.call(UNSERVABLE_TOOLS, toolName);
|
|
@@ -49474,6 +50651,7 @@ function getAllWrappedEndpoints() {
|
|
|
49474
50651
|
...getAllRwaEndpoints(),
|
|
49475
50652
|
...getAllArchiveEndpoints(),
|
|
49476
50653
|
...getAllPolymarketEndpoints(),
|
|
50654
|
+
...getAllKalshiEndpoints(),
|
|
49477
50655
|
...getAllSignalsEndpoints(),
|
|
49478
50656
|
...getAllBlockscoutEndpoints(),
|
|
49479
50657
|
...getAllGoldskyEndpoints(),
|
|
@@ -49552,6 +50730,7 @@ function getAllWrappedEndpoints() {
|
|
|
49552
50730
|
"rwa_",
|
|
49553
50731
|
"archive_",
|
|
49554
50732
|
"polymarket_",
|
|
50733
|
+
"kalshi_",
|
|
49555
50734
|
"signals_",
|
|
49556
50735
|
"fetch_",
|
|
49557
50736
|
"goldsky_"
|
|
@@ -49677,8 +50856,6 @@ var ToolRegistry = [
|
|
|
49677
50856
|
"rwa_get_funding_rates",
|
|
49678
50857
|
"rwa_get_markets",
|
|
49679
50858
|
"rwa_get_hyperliquid_dex_perps",
|
|
49680
|
-
"rwa_get_ostium_markets",
|
|
49681
|
-
"rwa_get_avantis_markets",
|
|
49682
50859
|
"rwa_get_lighter_markets",
|
|
49683
50860
|
"rwa_get_extended_markets",
|
|
49684
50861
|
// Hive Archive cross-venue derivatives history (since 2023-11).
|
|
@@ -50123,7 +51300,7 @@ var ToolRegistry = [
|
|
|
50123
51300
|
{
|
|
50124
51301
|
category: "Prediction Markets",
|
|
50125
51302
|
name: "get_prediction_markets_endpoints",
|
|
50126
|
-
description: "Endpoints for prediction-market intelligence on Polymarket: market and event search, market-implied odds per outcome,
|
|
51303
|
+
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
51304
|
tools: [
|
|
50128
51305
|
"polymarket_search_markets",
|
|
50129
51306
|
"polymarket_get_events",
|
|
@@ -50131,7 +51308,13 @@ var ToolRegistry = [
|
|
|
50131
51308
|
"polymarket_get_price_history",
|
|
50132
51309
|
"polymarket_get_orderbook",
|
|
50133
51310
|
"polymarket_get_wallet_positions",
|
|
50134
|
-
"polymarket_get_wallet_activity"
|
|
51311
|
+
"polymarket_get_wallet_activity",
|
|
51312
|
+
"kalshi_search_markets",
|
|
51313
|
+
"kalshi_get_events",
|
|
51314
|
+
"kalshi_get_market",
|
|
51315
|
+
"kalshi_get_orderbook",
|
|
51316
|
+
"kalshi_get_trades",
|
|
51317
|
+
"kalshi_get_price_history"
|
|
50135
51318
|
]
|
|
50136
51319
|
}
|
|
50137
51320
|
];
|
|
@@ -52931,7 +54114,7 @@ function formatSchemaIssues(error) {
|
|
|
52931
54114
|
}).join(" ");
|
|
52932
54115
|
}
|
|
52933
54116
|
function unknownKeysError(unknownKeys, accepted) {
|
|
52934
|
-
return
|
|
54117
|
+
return errorResult6(
|
|
52935
54118
|
`Invalid arguments: ${unknownKeys.map((key) => `"${key}" is not allowed.`).join(" ")} Accepted fields: ${accepted}.`
|
|
52936
54119
|
);
|
|
52937
54120
|
}
|
|
@@ -52946,11 +54129,11 @@ function looksLikeNameService(address) {
|
|
|
52946
54129
|
return /\.[a-z]{2,}$/i.test(address.trim());
|
|
52947
54130
|
}
|
|
52948
54131
|
function nameServiceError(address) {
|
|
52949
|
-
return
|
|
54132
|
+
return errorResult6(
|
|
52950
54133
|
`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
54134
|
);
|
|
52952
54135
|
}
|
|
52953
|
-
function
|
|
54136
|
+
function errorResult6(message, code = "VALIDATION_ERROR") {
|
|
52954
54137
|
return {
|
|
52955
54138
|
content: [{ type: "text", text: JSON.stringify({ error: message, code }) }],
|
|
52956
54139
|
isError: true
|
|
@@ -52984,7 +54167,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
52984
54167
|
const executeUnderlying = async (toolName, args, parentTool) => {
|
|
52985
54168
|
const lookup = resolveEndpoint(endpoints, toolName);
|
|
52986
54169
|
if (!lookup.ok) {
|
|
52987
|
-
return
|
|
54170
|
+
return errorResult6(
|
|
52988
54171
|
`Underlying endpoint '${toolName}' is not registered. Call search_tools to locate the current tool for this intent.`
|
|
52989
54172
|
);
|
|
52990
54173
|
}
|
|
@@ -53091,7 +54274,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53091
54274
|
}
|
|
53092
54275
|
const parsed = tokenPriceSchema.safeParse(heroArgs.args);
|
|
53093
54276
|
if (!parsed.success) {
|
|
53094
|
-
return
|
|
54277
|
+
return errorResult6(
|
|
53095
54278
|
`Invalid arguments: ${formatSchemaIssues(parsed.error)}`
|
|
53096
54279
|
);
|
|
53097
54280
|
}
|
|
@@ -53108,7 +54291,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53108
54291
|
return respond(result3, format, trimOnchainPrice(address));
|
|
53109
54292
|
}
|
|
53110
54293
|
if (!token) {
|
|
53111
|
-
return
|
|
54294
|
+
return errorResult6(
|
|
53112
54295
|
'Invalid arguments: pass token (CoinGecko id or symbol, for example "bitcoin" or "BTC"), or chain + address for a contract.'
|
|
53113
54296
|
);
|
|
53114
54297
|
}
|
|
@@ -53200,7 +54383,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53200
54383
|
}
|
|
53201
54384
|
const parsed = tokenSafetySchema.safeParse(heroArgs.args);
|
|
53202
54385
|
if (!parsed.success) {
|
|
53203
|
-
return
|
|
54386
|
+
return errorResult6(
|
|
53204
54387
|
`Invalid arguments: ${formatSchemaIssues(parsed.error)}`
|
|
53205
54388
|
);
|
|
53206
54389
|
}
|
|
@@ -53216,7 +54399,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53216
54399
|
const normalized = normalizeChain(chain);
|
|
53217
54400
|
const chainId = /^\d+$/.test(normalized) ? normalized : GOPLUS_CHAIN_IDS[normalized];
|
|
53218
54401
|
if (!chainId) {
|
|
53219
|
-
return
|
|
54402
|
+
return errorResult6(
|
|
53220
54403
|
`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
54404
|
);
|
|
53222
54405
|
}
|
|
@@ -53385,7 +54568,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53385
54568
|
}
|
|
53386
54569
|
const parsed = walletPortfolioSchema.safeParse(heroArgs.args);
|
|
53387
54570
|
if (!parsed.success) {
|
|
53388
|
-
return
|
|
54571
|
+
return errorResult6(
|
|
53389
54572
|
`Invalid arguments: ${formatSchemaIssues(parsed.error)}`
|
|
53390
54573
|
);
|
|
53391
54574
|
}
|
|
@@ -53424,7 +54607,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
53424
54607
|
fallbackNote = "CoinGecko wallet balances failed for this call; served from Alchemy.";
|
|
53425
54608
|
}
|
|
53426
54609
|
if (!network) {
|
|
53427
|
-
return
|
|
54610
|
+
return errorResult6(
|
|
53428
54611
|
`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
54612
|
);
|
|
53430
54613
|
}
|
|
@@ -54268,7 +55451,7 @@ var TASK_TOOLSET_ROUTES = {
|
|
|
54268
55451
|
steps: [
|
|
54269
55452
|
step(
|
|
54270
55453
|
"funding",
|
|
54271
|
-
"Read normalized funding across all
|
|
55454
|
+
"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
55455
|
"rwa_get_funding_rates",
|
|
54273
55456
|
"rwa_get_hyperliquid_dex_perps",
|
|
54274
55457
|
`${PROVIDER_FALLBACK} The flagship builder dexes carry the deepest RWA books when the full fan-out is unavailable.`
|
|
@@ -54310,7 +55493,7 @@ var TASK_TOOLSET_ROUTES = {
|
|
|
54310
55493
|
{
|
|
54311
55494
|
id: "rwa_builder_dex_scan",
|
|
54312
55495
|
label: "HIP-3 builder-dex scan",
|
|
54313
|
-
trigger: "Hyperliquid builder-dex, HIP-3, or pre-IPO equity perp questions (xyz,
|
|
55496
|
+
trigger: "Hyperliquid builder-dex, HIP-3, or pre-IPO equity perp questions (xyz, flx, cash, ...).",
|
|
54314
55497
|
exampleQuestion: "What is trading on Hyperliquid's builder dexes and what funding are the pre-IPO perps paying?",
|
|
54315
55498
|
requiredIdentifiers: ["builder dex name or pre-IPO underlying"],
|
|
54316
55499
|
maxMaterialCalls: 2,
|
|
@@ -55160,7 +56343,7 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
55160
56343
|
id: "rwa_perp_analysis",
|
|
55161
56344
|
kind: "product",
|
|
55162
56345
|
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,
|
|
56346
|
+
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
56347
|
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
56348
|
requiredIdentifiers: ["underlying ticker, venue, or asset class"],
|
|
55166
56349
|
optionalIdentifiers: ["builder dex name", "time range"],
|
|
@@ -55168,8 +56351,6 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
55168
56351
|
"rwa_get_funding_rates",
|
|
55169
56352
|
"rwa_get_markets",
|
|
55170
56353
|
"rwa_get_hyperliquid_dex_perps",
|
|
55171
|
-
"rwa_get_ostium_markets",
|
|
55172
|
-
"rwa_get_avantis_markets",
|
|
55173
56354
|
"rwa_get_lighter_markets",
|
|
55174
56355
|
"rwa_get_extended_markets",
|
|
55175
56356
|
"hyperliquid_get_perp_dexs",
|
|
@@ -55225,8 +56406,6 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
55225
56406
|
"carry",
|
|
55226
56407
|
"builder dex",
|
|
55227
56408
|
"hip-3",
|
|
55228
|
-
"ostium",
|
|
55229
|
-
"avantis",
|
|
55230
56409
|
"lighter",
|
|
55231
56410
|
"extended",
|
|
55232
56411
|
"price suspect"
|
|
@@ -55236,9 +56415,11 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
55236
56415
|
id: "prediction_markets",
|
|
55237
56416
|
kind: "product",
|
|
55238
56417
|
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.
|
|
56418
|
+
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
56419
|
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: [
|
|
56420
|
+
requiredIdentifiers: [
|
|
56421
|
+
"market topic, Polymarket slug or condition id, or Kalshi ticker"
|
|
56422
|
+
],
|
|
55242
56423
|
optionalIdentifiers: [
|
|
55243
56424
|
"CLOB token id",
|
|
55244
56425
|
"wallet address",
|
|
@@ -55252,7 +56433,13 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
55252
56433
|
"polymarket_get_price_history",
|
|
55253
56434
|
"polymarket_get_orderbook",
|
|
55254
56435
|
"polymarket_get_wallet_positions",
|
|
55255
|
-
"polymarket_get_wallet_activity"
|
|
56436
|
+
"polymarket_get_wallet_activity",
|
|
56437
|
+
"kalshi_search_markets",
|
|
56438
|
+
"kalshi_get_events",
|
|
56439
|
+
"kalshi_get_market",
|
|
56440
|
+
"kalshi_get_orderbook",
|
|
56441
|
+
"kalshi_get_trades",
|
|
56442
|
+
"kalshi_get_price_history"
|
|
55256
56443
|
],
|
|
55257
56444
|
fallbackTools: ["hyperliquid_get_outcome_meta"],
|
|
55258
56445
|
stableFixtures: [
|
|
@@ -58041,8 +59228,8 @@ var HiveOAuthProvider = class {
|
|
|
58041
59228
|
await this.storage.revokeFamily(record.familyId);
|
|
58042
59229
|
throw error;
|
|
58043
59230
|
}
|
|
58044
|
-
const
|
|
58045
|
-
if (
|
|
59231
|
+
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));
|
|
59232
|
+
if (invalid3) {
|
|
58046
59233
|
await this.storage.revokeFamily(record.familyId);
|
|
58047
59234
|
throw new InvalidGrantError(
|
|
58048
59235
|
"Refresh token is not valid for this client, resource, or scope set."
|
|
@@ -58873,7 +60060,7 @@ function parsePositiveInteger(value, fallback) {
|
|
|
58873
60060
|
const parsed = Number.parseInt(value || "", 10);
|
|
58874
60061
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
58875
60062
|
}
|
|
58876
|
-
function
|
|
60063
|
+
function sleep4(ms) {
|
|
58877
60064
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
58878
60065
|
}
|
|
58879
60066
|
function chainIdPathSegment(value) {
|
|
@@ -59179,7 +60366,7 @@ var GoPlusClient = class {
|
|
|
59179
60366
|
maxAttempts: this.rateLimitRetryAttempts + 1,
|
|
59180
60367
|
backoffMs
|
|
59181
60368
|
});
|
|
59182
|
-
await
|
|
60369
|
+
await sleep4(backoffMs);
|
|
59183
60370
|
}
|
|
59184
60371
|
if (result2 && typeof result2.code === "number" && result2.code !== 1 && result2.code !== 2) {
|
|
59185
60372
|
const isRateLimit = result2.code === 4029;
|
|
@@ -59630,6 +60817,9 @@ var CCXTClient = class {
|
|
|
59630
60817
|
if (this.exchanges.has(cacheKey2)) {
|
|
59631
60818
|
return this.exchanges.get(cacheKey2);
|
|
59632
60819
|
}
|
|
60820
|
+
if (REMOVED_CCXT_EXCHANGES.has(exchangeId.toLowerCase())) {
|
|
60821
|
+
throw new Error(removedVenueMessage(exchangeId));
|
|
60822
|
+
}
|
|
59633
60823
|
if (!ccxt[exchangeId]) {
|
|
59634
60824
|
throw new Error(
|
|
59635
60825
|
`Exchange '${exchangeId}' is not supported. Use one of: binance, coinbase, kraken, kucoin, etc.`
|
|
@@ -60490,6 +61680,7 @@ var PROVIDER_RESOURCE_PATTERNS = [
|
|
|
60490
61680
|
["rwa", /^rwa(?:[.-]|$)/],
|
|
60491
61681
|
["archive", /^archive(?:[.-]|$)/],
|
|
60492
61682
|
["polymarket", /^polymarket(?:[.-]|$)/],
|
|
61683
|
+
["kalshi", /^kalshi(?:[.-]|$)/],
|
|
60493
61684
|
["alternative", /^signals-fear-greed$/],
|
|
60494
61685
|
["blockchain", /^signals-onchain/],
|
|
60495
61686
|
["blockscout", /^blockscout(?:[.-]|$)/],
|
|
@@ -60516,6 +61707,7 @@ function classifyProvider(endpoint) {
|
|
|
60516
61707
|
if (toolName.startsWith("rwa_")) return "rwa";
|
|
60517
61708
|
if (toolName.startsWith("archive_")) return "archive";
|
|
60518
61709
|
if (toolName.startsWith("polymarket_")) return "polymarket";
|
|
61710
|
+
if (toolName.startsWith("kalshi_")) return "kalshi";
|
|
60519
61711
|
if (toolName === "signals_get_fear_greed") return "alternative";
|
|
60520
61712
|
if (toolName.startsWith("signals_get_onchain")) return "blockchain";
|
|
60521
61713
|
if (toolName.startsWith("fetch_")) return "fetch";
|
|
@@ -60544,8 +61736,8 @@ var QUICK_PROBE_ARGS = {
|
|
|
60544
61736
|
args: { network: "eth-mainnet" }
|
|
60545
61737
|
},
|
|
60546
61738
|
hyperliquid: { toolName: "hyperliquid_get_perp_dexs", args: {} },
|
|
60547
|
-
// Single-request keyless venue read (official
|
|
60548
|
-
rwa: { toolName: "
|
|
61739
|
+
// Single-request keyless venue read (official Extended markets endpoint).
|
|
61740
|
+
rwa: { toolName: "rwa_get_extended_markets", args: { limit: 5 } },
|
|
60549
61741
|
// Cheap coverage probe (index-only extremes, 10-min in-process cache).
|
|
60550
61742
|
// Unset archive credentials classify as missing-key skip, not failure.
|
|
60551
61743
|
archive: { toolName: "archive_get_coverage", args: {} },
|
|
@@ -61709,6 +62901,7 @@ function getClientForProvider(provider, clients) {
|
|
|
61709
62901
|
case "defillama":
|
|
61710
62902
|
case "archive":
|
|
61711
62903
|
case "polymarket":
|
|
62904
|
+
case "kalshi":
|
|
61712
62905
|
case "alternative":
|
|
61713
62906
|
case "blockchain":
|
|
61714
62907
|
case "goldsky":
|
|
@@ -61979,9 +63172,22 @@ function projectArgsToToolSchema(args, schema) {
|
|
|
61979
63172
|
Object.entries(args).filter(([key]) => key in properties)
|
|
61980
63173
|
);
|
|
61981
63174
|
}
|
|
63175
|
+
var UNPROBED_PROVIDERS = {
|
|
63176
|
+
kalshi: "Not probed: the Kalshi developer agreement (section 3.5) forbids probing its API for availability or performance."
|
|
63177
|
+
};
|
|
61982
63178
|
async function runEndpointProbe(endpoint, provider, clients, args, timeoutMs = PROBE_TIMEOUT) {
|
|
61983
63179
|
const start = Date.now();
|
|
61984
63180
|
const toolName = endpoint.tool.name;
|
|
63181
|
+
const unprobedReason = UNPROBED_PROVIDERS[provider];
|
|
63182
|
+
if (unprobedReason) {
|
|
63183
|
+
return {
|
|
63184
|
+
name: toolName,
|
|
63185
|
+
status: "skipped",
|
|
63186
|
+
duration: 0,
|
|
63187
|
+
error: unprobedReason,
|
|
63188
|
+
runtimeStatus: "ok"
|
|
63189
|
+
};
|
|
63190
|
+
}
|
|
61985
63191
|
const isWriteCapable = endpoint.metadata.operation === "write" || endpoint.tool.annotations?.readOnlyHint === false;
|
|
61986
63192
|
if (isWriteCapable && process.env.HIVE_E2E_ALLOW_WRITE_TOOLS !== "true") {
|
|
61987
63193
|
return {
|
|
@@ -62347,8 +63553,8 @@ var TASK_CANARY_PROBES = [
|
|
|
62347
63553
|
},
|
|
62348
63554
|
{
|
|
62349
63555
|
toolsetId: "rwa_perp_analysis",
|
|
62350
|
-
toolName: "
|
|
62351
|
-
description: "
|
|
63556
|
+
toolName: "rwa_get_extended_markets",
|
|
63557
|
+
description: "Extended RWA markets check (single-request keyless venue read).",
|
|
62352
63558
|
args: { limit: 5 }
|
|
62353
63559
|
},
|
|
62354
63560
|
{
|
|
@@ -64086,8 +65292,6 @@ var TOOLSET_INTENT_HINTS = {
|
|
|
64086
65292
|
"rwa perp",
|
|
64087
65293
|
"pre-ipo",
|
|
64088
65294
|
"pre ipo",
|
|
64089
|
-
"ostium",
|
|
64090
|
-
"avantis",
|
|
64091
65295
|
"rollover"
|
|
64092
65296
|
],
|
|
64093
65297
|
wallet_investigation: [
|
|
@@ -64402,7 +65606,7 @@ var TASK_RESULT_VALIDATION_OUTPUT_SCHEMA = {
|
|
|
64402
65606
|
warnings: { type: "array", items: { type: "string" } }
|
|
64403
65607
|
}
|
|
64404
65608
|
};
|
|
64405
|
-
function
|
|
65609
|
+
function errorResult7(message) {
|
|
64406
65610
|
return {
|
|
64407
65611
|
content: [
|
|
64408
65612
|
{
|
|
@@ -65027,12 +66231,12 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65027
66231
|
},
|
|
65028
66232
|
handler: async (_client, args) => {
|
|
65029
66233
|
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
|
|
66234
|
+
if (!args) return errorResult7(missingEndpoint);
|
|
65031
66235
|
const parsedArgs = getEndpointSchema.safeParse(args);
|
|
65032
|
-
if (!parsedArgs.success) return
|
|
66236
|
+
if (!parsedArgs.success) return errorResult7(missingEndpoint);
|
|
65033
66237
|
const parsedEndpointArgs = parsedArgs.data;
|
|
65034
66238
|
const requestedEndpoint = parsedEndpointArgs.endpoint ?? parsedEndpointArgs.endpoint_name ?? firstStringArg(args, ENDPOINT_NAME_ALIASES);
|
|
65035
|
-
if (!requestedEndpoint) return
|
|
66239
|
+
if (!requestedEndpoint) return errorResult7(missingEndpoint);
|
|
65036
66240
|
const lookup = resolveEndpoint(endpoints, requestedEndpoint);
|
|
65037
66241
|
const metaEndpoint = lookup.ok ? void 0 : [
|
|
65038
66242
|
searchToolsTool,
|
|
@@ -65080,12 +66284,12 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65080
66284
|
const invokeWithPolicy = async (invoker, client2, args) => {
|
|
65081
66285
|
const { success, data, error } = invokeEndpointSchema.safeParse(args);
|
|
65082
66286
|
if (!success) {
|
|
65083
|
-
return
|
|
66287
|
+
return errorResult7(
|
|
65084
66288
|
`Invalid arguments: ${JSON.stringify(error?.format())}`
|
|
65085
66289
|
);
|
|
65086
66290
|
}
|
|
65087
66291
|
const invokeArgs = normalizeInvokeArgs(data);
|
|
65088
|
-
if (!invokeArgs.ok) return
|
|
66292
|
+
if (!invokeArgs.ok) return errorResult7(invokeArgs.message);
|
|
65089
66293
|
const lookup = resolveEndpoint(endpoints, invokeArgs.resolvedToolName);
|
|
65090
66294
|
if (!lookup.ok) {
|
|
65091
66295
|
return createToolNotFoundResult(
|
|
@@ -65093,7 +66297,7 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65093
66297
|
).mcpResult;
|
|
65094
66298
|
}
|
|
65095
66299
|
if (!endpointAllowedForInvoker(invoker, lookup.endpoint)) {
|
|
65096
|
-
return
|
|
66300
|
+
return errorResult7(invokerPolicyMessage(invoker, lookup.endpoint));
|
|
65097
66301
|
}
|
|
65098
66302
|
const executionContext = {
|
|
65099
66303
|
transport: "dynamic",
|
|
@@ -65167,7 +66371,7 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65167
66371
|
handler: async (_client, args) => {
|
|
65168
66372
|
const parsed = validateTaskResultSchema.safeParse(args);
|
|
65169
66373
|
if (!parsed.success) {
|
|
65170
|
-
return
|
|
66374
|
+
return errorResult7(
|
|
65171
66375
|
`Invalid task result validation request: ${JSON.stringify(parsed.error.format())}`
|
|
65172
66376
|
);
|
|
65173
66377
|
}
|
|
@@ -65175,7 +66379,7 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
65175
66379
|
(entry2) => entry2.id === parsed.data.task_toolset_id
|
|
65176
66380
|
);
|
|
65177
66381
|
if (!toolset) {
|
|
65178
|
-
return
|
|
66382
|
+
return errorResult7(
|
|
65179
66383
|
`Unknown task toolset '${parsed.data.task_toolset_id}'. Use search_tools to select a canonical id.`
|
|
65180
66384
|
);
|
|
65181
66385
|
}
|
|
@@ -66635,8 +67839,6 @@ export {
|
|
|
66635
67839
|
runWithRequestPrincipal,
|
|
66636
67840
|
principalKeyFromRaw,
|
|
66637
67841
|
submitFeedback,
|
|
66638
|
-
HyperliquidAPI,
|
|
66639
|
-
collectRwaRows,
|
|
66640
67842
|
isFreeWriteEndpoint,
|
|
66641
67843
|
trackServerEvent,
|
|
66642
67844
|
trackCliCommand,
|