hive-intelligence 1.8.0 → 1.10.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.
@@ -4457,6 +4457,52 @@ var handler42 = async (client2, args) => {
4457
4457
  };
4458
4458
  var get_timeframe_pools_networks_onchain_ohlcv_default = { metadata: metadata42, tool: tool42, handler: handler42 };
4459
4459
 
4460
+ // src/tools/onchain/tradeSummary.ts
4461
+ var UPSTREAM_TRADE_CAP = 300;
4462
+ function usd(value) {
4463
+ const n = typeof value === "string" ? Number(value) : value;
4464
+ return typeof n === "number" && Number.isFinite(n) ? n : 0;
4465
+ }
4466
+ function withTradeSummary(payload, minTradeUsd) {
4467
+ const rows = payload?.data;
4468
+ if (!Array.isArray(rows)) return payload;
4469
+ let buys = 0;
4470
+ let sells = 0;
4471
+ let buyVolume = 0;
4472
+ let sellVolume = 0;
4473
+ const timestamps = [];
4474
+ for (const row of rows) {
4475
+ const attrs = row?.attributes ?? {};
4476
+ if (attrs.kind === "buy") {
4477
+ buys += 1;
4478
+ buyVolume += usd(attrs.volume_in_usd);
4479
+ } else if (attrs.kind === "sell") {
4480
+ sells += 1;
4481
+ sellVolume += usd(attrs.volume_in_usd);
4482
+ }
4483
+ if (typeof attrs.block_timestamp === "string") {
4484
+ timestamps.push(attrs.block_timestamp);
4485
+ }
4486
+ }
4487
+ timestamps.sort();
4488
+ const filtered = usd(minTradeUsd) > 0;
4489
+ return {
4490
+ summary: {
4491
+ trade_count: rows.length,
4492
+ buys,
4493
+ sells,
4494
+ buy_volume_usd: buyVolume,
4495
+ sell_volume_usd: sellVolume,
4496
+ oldest_trade_at: timestamps[0] ?? null,
4497
+ newest_trade_at: timestamps.at(-1) ?? null,
4498
+ min_trade_usd: filtered ? usd(minTradeUsd) : 0,
4499
+ upstream_capped: rows.length >= UPSTREAM_TRADE_CAP,
4500
+ note: `Counts cover every trade GeckoTerminal returned: at most ${UPSTREAM_TRADE_CAP}, newest first, within the last 24h${filtered ? `, trades of at least $${usd(minTradeUsd)} only` : ""}. The data array may be cut to the newest rows by the response budget, so count buys and sells from this summary, not from data. Pool transactions.h24 (get_token_pools) counts every swap and can exceed trade_count when the cap or the minimum-size filter applies.`
4501
+ },
4502
+ ...payload
4503
+ };
4504
+ }
4505
+
4460
4506
  // src/tools/onchain/networks/pools/trades/get-pools-networks-onchain-trades.ts
4461
4507
  var metadata43 = {
4462
4508
  resource: "onchain.networks.pools.trades",
@@ -4555,7 +4601,12 @@ Error Handling:
4555
4601
  };
4556
4602
  var handler43 = async (client2, args) => {
4557
4603
  const { pool_address, ...body } = args;
4558
- return asTextContentResult(await client2.onchain.networks.pools.trades.get(pool_address, body));
4604
+ return asTextContentResult(
4605
+ withTradeSummary(
4606
+ await client2.onchain.networks.pools.trades.get(pool_address, body),
4607
+ body.trade_volume_in_usd_greater_than
4608
+ )
4609
+ );
4559
4610
  };
4560
4611
  var get_pools_networks_onchain_trades_default = { metadata: metadata43, tool: tool43, handler: handler43 };
4561
4612
 
@@ -4745,6 +4796,8 @@ var handler45 = async (client2, args) => {
4745
4796
  var get_tokens_networks_onchain_info_default = { metadata: metadata45, tool: tool45, handler: handler45 };
4746
4797
 
4747
4798
  // src/tools/onchain/networks/tokens/top-holders/get-tokens-networks-onchain-top-holders.ts
4799
+ var TOP_HOLDERS_MAX = 50;
4800
+ var SOLANA_TOP_HOLDERS_MAX = 40;
4748
4801
  var metadata46 = {
4749
4802
  resource: "onchain.networks.tokens.top_holders",
4750
4803
  operation: "read",
@@ -4769,7 +4822,8 @@ Returns a ranked list of addresses holding the largest amounts of a token. Essen
4769
4822
  Args:
4770
4823
  - network (string, required): Network ID (e.g., "eth", "bsc", "solana"). Use coingecko_get_onchain_networks to find valid IDs.
4771
4824
  - address (string, required): Token contract address.
4772
- - holders (string, optional): Number of top holders to return. Use any integer or "max". Default: 10.
4825
+ - limit (integer, optional): Number of top holders to return, 1-50 (Solana: 1-40, the most Hive's CoinGecko plan serves). Default: the plan maximum.
4826
+ - holders (string, optional): Legacy alias for limit; an integer or "max". Values above the plan maximum, and "max", are capped to it.
4773
4827
  - include_pnl_details (boolean, optional): Include realized/unrealized P&L per holder (pnl_usd, cost_basis_usd, avg_buy_price_usd). Default: false.
4774
4828
 
4775
4829
  Returns:
@@ -4793,9 +4847,8 @@ Returns:
4793
4847
  }
4794
4848
 
4795
4849
  Examples:
4796
- - Top 10 USDC holders: {"network": "eth", "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"}
4797
- - Top 50 holders: {"network": "eth", "address": "0xa0b86991...", "holders": "50"}
4798
- - All holders: {"network": "bsc", "address": "0x...", "holders": "max"}
4850
+ - Top 50 USDC holders: {"network": "eth", "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"}
4851
+ - Top 10 holders: {"network": "eth", "address": "0xa0b86991...", "limit": 10}
4799
4852
 
4800
4853
  Use when:
4801
4854
  - You need to identify major token holders (whales)
@@ -4804,6 +4857,7 @@ Use when:
4804
4857
  - You're tracking institutional or smart money positions
4805
4858
 
4806
4859
  Don't use when:
4860
+ - You need the total holder count or how many holders are above a USD size (use get_token_holder_distribution)
4807
4861
  - You need historical holder count (use coingecko_get_onchain_token_holders_chart)
4808
4862
  - You need token metadata (use coingecko_get_onchain_token_info)
4809
4863
  - You need trading data (use coingecko_get_onchain_token_trades)
@@ -4825,9 +4879,15 @@ Error Handling:
4825
4879
  type: "string",
4826
4880
  description: "Token contract address. Required."
4827
4881
  },
4882
+ limit: {
4883
+ type: "integer",
4884
+ minimum: 1,
4885
+ maximum: TOP_HOLDERS_MAX,
4886
+ description: `Number of top holders to return, 1-${TOP_HOLDERS_MAX} (Solana: 1-${SOLANA_TOP_HOLDERS_MAX}). Default: the plan maximum.`
4887
+ },
4828
4888
  holders: {
4829
4889
  type: "string",
4830
- description: 'Number of top holders to return, or "max". Default: 10.'
4890
+ description: 'Legacy alias for limit: an integer or "max". Capped to the plan maximum.'
4831
4891
  },
4832
4892
  include_pnl_details: {
4833
4893
  type: "boolean",
@@ -4838,10 +4898,19 @@ Error Handling:
4838
4898
  },
4839
4899
  annotations: VOLATILE_READ_ANNOTATIONS
4840
4900
  };
4901
+ function resolveTopHoldersCount(network, limit, holders) {
4902
+ const cap = String(network ?? "").toLowerCase() === "solana" ? SOLANA_TOP_HOLDERS_MAX : TOP_HOLDERS_MAX;
4903
+ const requested = Number(limit ?? (holders === "max" ? cap : holders ?? cap));
4904
+ if (!Number.isFinite(requested) || requested < 1) return String(cap);
4905
+ return String(Math.min(Math.floor(requested), cap));
4906
+ }
4841
4907
  var handler46 = async (client2, args) => {
4842
- const { address, ...body } = args;
4908
+ const { address, limit, holders, ...body } = args ?? {};
4843
4909
  return asTextContentResult(
4844
- await client2.onchain.networks.tokens.topHolders.get(address, body)
4910
+ await client2.onchain.networks.tokens.topHolders.get(address, {
4911
+ ...body,
4912
+ holders: resolveTopHoldersCount(body.network, limit, holders)
4913
+ })
4845
4914
  );
4846
4915
  };
4847
4916
  var get_tokens_networks_onchain_top_holders_default = { metadata: metadata46, tool: tool46, handler: handler46 };
@@ -5255,7 +5324,12 @@ Error Handling:
5255
5324
  };
5256
5325
  var handler50 = async (client2, args) => {
5257
5326
  const { token_address, ...body } = args;
5258
- return asTextContentResult(await client2.onchain.networks.tokens.trades.get(token_address, body));
5327
+ return asTextContentResult(
5328
+ withTradeSummary(
5329
+ await client2.onchain.networks.tokens.trades.get(token_address, body),
5330
+ body.trade_volume_in_usd_greater_than
5331
+ )
5332
+ );
5259
5333
  };
5260
5334
  var get_tokens_networks_onchain_trades_default = { metadata: metadata50, tool: tool50, handler: handler50 };
5261
5335
 
@@ -18117,6 +18191,16 @@ var wrapper = {
18117
18191
  oldToolName: "ccxt_fetch_convert_quote"
18118
18192
  }
18119
18193
  }
18194
+ ],
18195
+ // Blockscout tools describe themselves; these entries only admit the
18196
+ // unprefixed name (src/tools/blockscout/tools.ts).
18197
+ getAllBlockscoutEndpoints: [
18198
+ {
18199
+ tool: {
18200
+ name: "get_token_holder_distribution",
18201
+ oldToolName: "get_token_holder_distribution"
18202
+ }
18203
+ }
18120
18204
  ]
18121
18205
  };
18122
18206
 
@@ -46134,7 +46218,7 @@ function getAllAlchemyEndpoints() {
46134
46218
  import axios3 from "axios";
46135
46219
  var HYPERLIQUID_API_URL = "https://api.hyperliquid.xyz";
46136
46220
  var DEFAULT_LEADERBOARD_LIMIT = 20;
46137
- var MAX_LEADERBOARD_LIMIT = 100;
46221
+ var MAX_LEADERBOARD_LIMIT = 500;
46138
46222
  var FUNDING_HISTORY_PAGE_SIZE = 500;
46139
46223
  var DEFAULT_FUNDING_HISTORY_RETURN_POINTS = 500;
46140
46224
  var MAX_FUNDING_HISTORY_RETURN_POINTS = 500;
@@ -46177,7 +46261,7 @@ var HyperliquidAPI = class {
46177
46261
  * Omit (or pass empty) for the main Hyperliquid DEX.
46178
46262
  * @param limit - top N pairs by 24h notional volume (1-100, default 20).
46179
46263
  */
46180
- async getPerpVolumeLeaderboard(dex, limit = DEFAULT_LEADERBOARD_LIMIT) {
46264
+ async getPerpVolumeLeaderboard(dex, limit = DEFAULT_LEADERBOARD_LIMIT, offset = 0) {
46181
46265
  const body = { type: "metaAndAssetCtxs" };
46182
46266
  if (dex) body.dex = dex;
46183
46267
  const { data } = await this.client.post("/info", body);
@@ -46202,6 +46286,7 @@ var HyperliquidAPI = class {
46202
46286
  Math.max(1, Math.floor(limit || DEFAULT_LEADERBOARD_LIMIT)),
46203
46287
  MAX_LEADERBOARD_LIMIT
46204
46288
  );
46289
+ const safeOffset = Math.max(0, Math.floor(offset || 0));
46205
46290
  return {
46206
46291
  dex: dex || "main",
46207
46292
  pairCount: rows.length,
@@ -46209,7 +46294,8 @@ var HyperliquidAPI = class {
46209
46294
  (sum, row) => sum + row.dayNotionalVolume,
46210
46295
  0
46211
46296
  ),
46212
- leaderboard: rows.slice(0, safeLimit)
46297
+ offset: safeOffset,
46298
+ leaderboard: rows.slice(safeOffset, safeOffset + safeLimit)
46213
46299
  };
46214
46300
  }
46215
46301
  /**
@@ -46438,6 +46524,85 @@ var HyperliquidAPI = class {
46438
46524
  }
46439
46525
  };
46440
46526
 
46527
+ // src/config/responseLimits.ts
46528
+ var RESPONSE_LIMITS = {
46529
+ // Maximum tokens allowed in a response (MCP constraint)
46530
+ MAX_RESPONSE_TOKENS: 25e3,
46531
+ // Estimated characters per token (rough approximation)
46532
+ CHARS_PER_TOKEN: 4,
46533
+ // Maximum characters allowed (25000 * 4 = 100,000)
46534
+ MAX_RESPONSE_CHARS: 1e5,
46535
+ // Default pagination limits for different endpoints
46536
+ DEFAULT_LIMITS: {
46537
+ tokens: 100,
46538
+ pools: 50,
46539
+ trades: 100,
46540
+ holders: 100,
46541
+ events: 200,
46542
+ nfts: 50
46543
+ },
46544
+ // Maximum allowed limits to prevent oversized responses
46545
+ MAX_LIMITS: {
46546
+ tokens: 100,
46547
+ pools: 50,
46548
+ trades: 200,
46549
+ holders: 200,
46550
+ events: 300,
46551
+ nfts: 100
46552
+ },
46553
+ // Size thresholds for automatic truncation (in characters)
46554
+ TRUNCATION_THRESHOLDS: {
46555
+ warning: 8e4,
46556
+ // Start warning at 80KB
46557
+ truncate: 1e5
46558
+ // Force truncation at 100KB
46559
+ }
46560
+ };
46561
+
46562
+ // src/utils/responseHeadroom.ts
46563
+ var MAX_RESULT_SIZE_META_KEY = "anthropic/maxResultSizeChars";
46564
+ var ANNOTATED_MAX_RESULT_SIZE_CHARS = RESPONSE_LIMITS.MAX_RESPONSE_CHARS;
46565
+ var DEFAULT_RESULT_SIZE_GUARD_CHARS = 2e4;
46566
+ var HEADROOM_TOOL_PATTERNS = [
46567
+ // Depth-of-book: order books, l2/l3 books.
46568
+ /orderbook|order_book|l2_book|l3_orderbook/i,
46569
+ // Holder / owner lists.
46570
+ /holders?(?:_|$)|owners_for/i,
46571
+ // OHLCV, candles, charts, and history/time-series families.
46572
+ /ohlc|candle|kline|market_chart|chart|history|timeseries/i
46573
+ ];
46574
+ function toolNeedsResponseHeadroom(toolName) {
46575
+ return HEADROOM_TOOL_PATTERNS.some((pattern) => pattern.test(toolName));
46576
+ }
46577
+ function getAnnotatedMaxResultSizeChars(tool96) {
46578
+ const meta2 = tool96?._meta;
46579
+ const value = meta2?.[MAX_RESULT_SIZE_META_KEY];
46580
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
46581
+ }
46582
+ function withResponseHeadroomMeta(tool96) {
46583
+ if (!toolNeedsResponseHeadroom(tool96.name)) return tool96;
46584
+ const existing = tool96._meta;
46585
+ if (existing && existing[MAX_RESULT_SIZE_META_KEY] !== void 0) return tool96;
46586
+ return {
46587
+ ...tool96,
46588
+ _meta: {
46589
+ ...existing ?? {},
46590
+ [MAX_RESULT_SIZE_META_KEY]: ANNOTATED_MAX_RESULT_SIZE_CHARS
46591
+ }
46592
+ };
46593
+ }
46594
+ function largestFittingRowCount(max, fits) {
46595
+ if (max <= 0 || fits(max)) return Math.max(0, max);
46596
+ let lo = 1;
46597
+ let hi = max - 1;
46598
+ while (lo < hi) {
46599
+ const mid = Math.ceil((lo + hi) / 2);
46600
+ if (fits(mid)) lo = mid;
46601
+ else hi = mid - 1;
46602
+ }
46603
+ return lo;
46604
+ }
46605
+
46441
46606
  // src/tools/hyperliquid/infoTools.ts
46442
46607
  var MAX_ARRAY_ITEMS = 500;
46443
46608
  var SPOT_TOKEN_TTL_MS = 10 * 60 * 1e3;
@@ -47376,14 +47541,19 @@ Hyperliquid is the dominant on-chain perpetual DEX. This reads the venue's own p
47376
47541
 
47377
47542
  Args:
47378
47543
  - dex (string, optional): Builder DEX name to scope the leaderboard to (e.g. "xyz" for tokenized-stock / pre-IPO perps like SPCX, MU, XYZ100). Omit for Hyperliquid's main perp DEX. Call hyperliquid_get_perp_dexs to discover builder DEX names.
47379
- - limit (number, optional): Number of top pairs to return, ranked by 24h notional volume (1-100, default 20).
47544
+ - limit (number, optional): Number of top pairs to return, ranked by 24h notional volume (1-${MAX_LEADERBOARD_LIMIT}, default 20). A page also stops at the ~${DEFAULT_RESULT_SIZE_GUARD_CHARS / 1e3}k-character response budget (about 100 pairs); page on with offset=next_offset to read every pair.
47545
+ - offset (number, optional): Rank to start from (0 = top pair, default 0).
47380
47546
 
47381
47547
  Returns:
47382
47548
  JSON object:
47383
47549
  {
47384
47550
  "dex": string, // "main" or the builder DEX name
47385
47551
  "pairCount": number, // total pairs on this DEX
47386
- "totalDayNotionalVolume": number, // summed 24h notional volume (USD)
47552
+ "totalDayNotionalVolume": number, // summed 24h notional volume over ALL pairs (USD), not just this page
47553
+ "offset": number, // rank of the first row
47554
+ "returnedCount": number, // rows in this response
47555
+ "next_offset": number | null, // pass as offset for the next page; null after the last pair
47556
+ "pagination_note"?: string, // present when the response budget cut the requested page short
47387
47557
  "leaderboard": [{
47388
47558
  "coin": string, // perp symbol (e.g. "BTC", "HYPE", "SPCX")
47389
47559
  "dayNotionalVolume": number, // 24h notional volume (USD)
@@ -47414,8 +47584,13 @@ Don't use when:
47414
47584
  limit: {
47415
47585
  type: "integer",
47416
47586
  minimum: 1,
47417
- maximum: 100,
47418
- description: "Top N pairs by 24h notional volume (1-100, default 20)."
47587
+ maximum: MAX_LEADERBOARD_LIMIT,
47588
+ description: `Top N pairs by 24h notional volume (1-${MAX_LEADERBOARD_LIMIT}, default 20). One page carries about 100 pairs (response budget); returnedCount and next_offset say what shipped.`
47589
+ },
47590
+ offset: {
47591
+ type: "integer",
47592
+ minimum: 0,
47593
+ description: "Rank to start from (0 = top pair). Pass the previous response's next_offset for the next page."
47419
47594
  }
47420
47595
  }
47421
47596
  },
@@ -47424,8 +47599,29 @@ Don't use when:
47424
47599
  handler: async (_client, args) => {
47425
47600
  const dex = typeof args?.dex === "string" ? args.dex : void 0;
47426
47601
  const limit = typeof args?.limit === "number" ? args.limit : void 0;
47427
- const data = await api.getPerpVolumeLeaderboard(dex, limit);
47428
- return asTextContentResult(data);
47602
+ const offset = typeof args?.offset === "number" ? args.offset : 0;
47603
+ const data = await api.getPerpVolumeLeaderboard(dex, limit, offset);
47604
+ const requested = data.leaderboard;
47605
+ const page = (kept) => {
47606
+ const next = data.offset + kept;
47607
+ return {
47608
+ ...data,
47609
+ returnedCount: kept,
47610
+ next_offset: next < data.pairCount ? next : null,
47611
+ ...kept < requested.length && {
47612
+ pagination_note: `${kept} of the ${requested.length} requested pairs fit the response budget. Re-call with offset=${next} for the next page.`
47613
+ },
47614
+ leaderboard: requested.slice(0, kept)
47615
+ };
47616
+ };
47617
+ return asTextContentResult(
47618
+ page(
47619
+ largestFittingRowCount(
47620
+ requested.length,
47621
+ (kept) => JSON.stringify(page(kept)).length <= DEFAULT_RESULT_SIZE_GUARD_CHARS - 500
47622
+ )
47623
+ )
47624
+ );
47429
47625
  }
47430
47626
  },
47431
47627
  {
@@ -48599,6 +48795,7 @@ async function collectRwaRows(deps, venues = RWA_VENUE_IDS) {
48599
48795
  // src/tools/rwa/tools.ts
48600
48796
  var DEFAULT_LIMIT = 100;
48601
48797
  var MAX_LIMIT = 500;
48798
+ var PAGE_BUDGET_CHARS = DEFAULT_RESULT_SIZE_GUARD_CHARS - 500;
48602
48799
  var ASSET_CLASSES = [
48603
48800
  "equity",
48604
48801
  "pre_ipo",
@@ -48620,6 +48817,10 @@ function clampLimit2(value) {
48620
48817
  if (!Number.isFinite(parsed)) return DEFAULT_LIMIT;
48621
48818
  return Math.min(Math.max(parsed, 1), MAX_LIMIT);
48622
48819
  }
48820
+ function clampOffset(value) {
48821
+ const parsed = typeof value === "number" ? Math.floor(value) : NaN;
48822
+ return Number.isFinite(parsed) ? Math.max(parsed, 0) : 0;
48823
+ }
48623
48824
  function sortRows(rows) {
48624
48825
  return [...rows].sort((a, b) => {
48625
48826
  const oiA = a.open_interest_usd ?? -1;
@@ -48657,15 +48858,30 @@ function applyFilters(rows, args) {
48657
48858
  }
48658
48859
  };
48659
48860
  }
48660
- function boundedEnvelope(rows, limit) {
48861
+ function boundedPayload(rows, args, render) {
48661
48862
  const sorted = sortRows(rows);
48662
- const items = sorted.slice(0, limit);
48663
- return {
48664
- count: sorted.length,
48665
- returnedCount: items.length,
48666
- truncated: items.length < sorted.length,
48667
- rows: items
48863
+ const offset = clampOffset(args?.offset);
48864
+ const requested = sorted.slice(offset, offset + clampLimit2(args?.limit));
48865
+ const build = (kept) => {
48866
+ const next = offset + kept;
48867
+ const page = {
48868
+ count: sorted.length,
48869
+ offset,
48870
+ returnedCount: kept,
48871
+ truncated: next < sorted.length,
48872
+ next_offset: next < sorted.length ? next : null,
48873
+ ...kept < requested.length && {
48874
+ pagination_note: `${kept} of the ${requested.length} requested rows fit the response budget. Re-call with offset=${next} for the next page.`
48875
+ }
48876
+ };
48877
+ return render(page, requested.slice(0, kept));
48668
48878
  };
48879
+ return build(
48880
+ largestFittingRowCount(
48881
+ requested.length,
48882
+ (kept) => JSON.stringify(build(kept)).length <= PAGE_BUDGET_CHARS
48883
+ )
48884
+ );
48669
48885
  }
48670
48886
  var FILTER_PARAM_PROPERTIES = {
48671
48887
  underlying: {
@@ -48685,7 +48901,12 @@ var FILTER_PARAM_PROPERTIES = {
48685
48901
  type: "integer",
48686
48902
  minimum: 1,
48687
48903
  maximum: MAX_LIMIT,
48688
- description: `Maximum rows to return (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}). Rows are ordered by open interest (deepest first).`
48904
+ description: `Maximum rows to return (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}). Rows are ordered by open interest (deepest first). A page also stops at the ~${DEFAULT_RESULT_SIZE_GUARD_CHARS / 1e3}k-character response budget (about 30 full rows); returnedCount is what shipped and next_offset continues.`
48905
+ },
48906
+ offset: {
48907
+ type: "integer",
48908
+ minimum: 0,
48909
+ description: "Rows to skip in the open-interest order (default 0). Pass the previous response's next_offset to read the next page."
48689
48910
  }
48690
48911
  };
48691
48912
  function toMarketRow(row) {
@@ -48733,9 +48954,10 @@ Args:
48733
48954
  - underlying (string, optional): filter to one canonical underlying ticker.
48734
48955
  - asset_class (string, optional): filter to one asset class.
48735
48956
  - limit (integer, optional): max rows (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}), deepest open interest first.
48957
+ - offset (integer, optional): rows to skip; pass next_offset to page.
48736
48958
 
48737
48959
  Returns:
48738
- Bounded JSON envelope: { "venue", "count", "returnedCount", "truncated", "rows": [normalized rows] }. ${returnsNote}
48960
+ Bounded JSON envelope: { "venue", "count", "offset", "returnedCount", "truncated", "next_offset", "pagination_note"?, "rows": [normalized rows] }. A page holds at most what fits the response budget (about 30 rows): returnedCount is the rows shipped, next_offset is null on the last page. ${returnsNote}
48739
48961
 
48740
48962
  Use when:
48741
48963
  ${useWhen.map((line) => ` - ${line}`).join("\n")}
@@ -48747,7 +48969,8 @@ ${dontUseWhen.map((line) => ` - ${line}`).join("\n")}`,
48747
48969
  properties: {
48748
48970
  underlying: FILTER_PARAM_PROPERTIES.underlying,
48749
48971
  asset_class: FILTER_PARAM_PROPERTIES.asset_class,
48750
- limit: FILTER_PARAM_PROPERTIES.limit
48972
+ limit: FILTER_PARAM_PROPERTIES.limit,
48973
+ offset: FILTER_PARAM_PROPERTIES.offset
48751
48974
  }
48752
48975
  },
48753
48976
  annotations: VOLATILE_READ_ANNOTATIONS
@@ -48756,13 +48979,15 @@ ${dontUseWhen.map((line) => ` - ${line}`).join("\n")}`,
48756
48979
  const { rows, note } = await fetchRwaVenueRows(venueId, deps);
48757
48980
  stampPriceSuspect(rows);
48758
48981
  const { filtered, filters } = applyFilters(rows, args);
48759
- const envelope = boundedEnvelope(filtered, clampLimit2(args?.limit));
48760
- return asTextContentResult({
48761
- venue: venueId,
48762
- ...note ? { note } : {},
48763
- filters,
48764
- ...envelope
48765
- });
48982
+ return asTextContentResult(
48983
+ boundedPayload(filtered, args, (page, pageRows) => ({
48984
+ venue: venueId,
48985
+ ...note ? { note } : {},
48986
+ filters,
48987
+ ...page,
48988
+ rows: pageRows
48989
+ }))
48990
+ );
48766
48991
  }
48767
48992
  };
48768
48993
  }
@@ -48798,6 +49023,7 @@ Args:
48798
49023
  - venue (string, optional): filter to one venue ("ostium", "avantis", "lighter", "extended", "hyperliquid", or "hyperliquid:xyz").
48799
49024
  - asset_class (string, optional): filter to one asset class (equity, pre_ipo, etf, index, commodity, fx, crypto).
48800
49025
  - limit (integer, optional): max rows (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}), deepest open interest first.
49026
+ - offset (integer, optional): rows to skip; pass next_offset to page.
48801
49027
 
48802
49028
  Returns:
48803
49029
  JSON object:
@@ -48805,9 +49031,11 @@ Returns:
48805
49031
  "filters": { "underlying", "venue", "asset_class" },
48806
49032
  "venueCount": number, "okCount": number,
48807
49033
  "venues": [{ "venue", "runtime_status", "rowCount", "error"? }],
48808
- "count": number, "returnedCount": number, "truncated": boolean,
49034
+ "count": number, "offset": number, "returnedCount": number, "truncated": boolean,
49035
+ "next_offset": number | null, "pagination_note"?: string,
48809
49036
  "rows": [normalized funding rows]
48810
49037
  }
49038
+ A page holds at most what fits the response budget (about 30 rows): returnedCount is the rows shipped; re-call with offset=next_offset for more.
48811
49039
 
48812
49040
  Use when:
48813
49041
  - "TSLA perp funding across venues" / tokenized-stock or equity perp funding questions
@@ -48827,17 +49055,19 @@ Don't use when:
48827
49055
  handler: async (_client, args) => {
48828
49056
  const { results, rows } = await collectRwaRows(deps);
48829
49057
  const { filtered, filters } = applyFilters(rows, args);
48830
- const envelope = boundedEnvelope(filtered, clampLimit2(args?.limit));
48831
49058
  const okCount = results.filter(
48832
49059
  (result2) => result2.runtime_status === "ok"
48833
49060
  ).length;
48834
- return asTextContentResult({
48835
- filters,
48836
- venueCount: results.length,
48837
- okCount,
48838
- venues: results,
48839
- ...envelope
48840
- });
49061
+ return asTextContentResult(
49062
+ boundedPayload(filtered, args, (page, pageRows) => ({
49063
+ filters,
49064
+ venueCount: results.length,
49065
+ okCount,
49066
+ venues: results,
49067
+ ...page,
49068
+ rows: pageRows
49069
+ }))
49070
+ );
48841
49071
  }
48842
49072
  },
48843
49073
  {
@@ -48864,6 +49094,7 @@ Args:
48864
49094
  - venue (string, optional): filter to one venue.
48865
49095
  - asset_class (string, optional): filter to one asset class (equity, pre_ipo, etf, index, commodity, fx, crypto).
48866
49096
  - limit (integer, optional): max rows (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}), deepest open interest first.
49097
+ - offset (integer, optional): rows to skip; pass next_offset to page.
48867
49098
 
48868
49099
  Returns:
48869
49100
  JSON object:
@@ -48871,7 +49102,7 @@ Returns:
48871
49102
  "filters": {...},
48872
49103
  "venueCount": number, "okCount": number,
48873
49104
  "venues": [{ "venue", "runtime_status", "rowCount", "error"? }],
48874
- "count", "returnedCount", "truncated",
49105
+ "count", "offset", "returnedCount", "truncated", "next_offset", "pagination_note"?,
48875
49106
  "rows": [{ "venue", "market", "symbol", "underlying", "asset_class", "funding_mechanism", "is_delisted", "mark_price", "oracle_price", "open_interest_usd", "volume_24h_usd", "is_price_suspect", "price_suspect_basis", "price_deviation_pct", "meta" }]
48876
49107
  }
48877
49108
 
@@ -48892,20 +49123,19 @@ Don't use when:
48892
49123
  handler: async (_client, args) => {
48893
49124
  const { results, rows } = await collectRwaRows(deps);
48894
49125
  const { filtered, filters } = applyFilters(rows, args);
48895
- const envelope = boundedEnvelope(filtered, clampLimit2(args?.limit));
48896
49126
  const okCount = results.filter(
48897
49127
  (result2) => result2.runtime_status === "ok"
48898
49128
  ).length;
48899
- return asTextContentResult({
48900
- filters,
48901
- venueCount: results.length,
48902
- okCount,
48903
- venues: results,
48904
- count: envelope.count,
48905
- returnedCount: envelope.returnedCount,
48906
- truncated: envelope.truncated,
48907
- rows: envelope.rows.map(toMarketRow)
48908
- });
49129
+ return asTextContentResult(
49130
+ boundedPayload(filtered, args, (page, pageRows) => ({
49131
+ filters,
49132
+ venueCount: results.length,
49133
+ okCount,
49134
+ venues: results,
49135
+ ...page,
49136
+ rows: pageRows.map(toMarketRow)
49137
+ }))
49138
+ );
48909
49139
  }
48910
49140
  },
48911
49141
  // -----------------------------------------------------------------
@@ -49029,7 +49259,14 @@ function getArchiveClient() {
49029
49259
  // src/tools/archive/api.ts
49030
49260
  var DEFAULT_LIMIT2 = 100;
49031
49261
  var MAX_LIMIT2 = 500;
49032
- var ARCHIVE_SINCE_NOTE = "archive since 2023-11";
49262
+ var ARCHIVE_SINCE = {
49263
+ funding: "2023-11",
49264
+ open_interest: "2025-04",
49265
+ basis: "2025-03",
49266
+ long_short: "2026-03",
49267
+ liquidations: "2025-04"
49268
+ };
49269
+ var ARCHIVE_SINCE_NOTE = `archive since ${ARCHIVE_SINCE.funding}`;
49033
49270
  var QUERY_TIMEOUT_MS = 1e4;
49034
49271
  var VENUE_SCAN_MAX_STEPS = 64;
49035
49272
  var COVERAGE_CACHE_TTL_MS = 10 * 60 * 1e3;
@@ -49323,7 +49560,7 @@ function historyHandler(toolName, dataset, options) {
49323
49560
  dataset: spec.table,
49324
49561
  runtime_status: "ok",
49325
49562
  fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
49326
- note: options?.extraNote ? `${ARCHIVE_SINCE_NOTE}. ${options.extraNote}` : ARCHIVE_SINCE_NOTE,
49563
+ note: options?.extraNote ? `archive since ${ARCHIVE_SINCE[dataset]}. ${options.extraNote}` : `archive since ${ARCHIVE_SINCE[dataset]}`,
49327
49564
  filters,
49328
49565
  order: `${spec.tsColumn} desc`,
49329
49566
  returnedCount: mapped.length,
@@ -49678,74 +49915,6 @@ async function fetchPolymarketJson(url, label, timeoutMs = POLYMARKET_TIMEOUT_MS
49678
49915
  }
49679
49916
  }
49680
49917
 
49681
- // src/config/responseLimits.ts
49682
- var RESPONSE_LIMITS = {
49683
- // Maximum tokens allowed in a response (MCP constraint)
49684
- MAX_RESPONSE_TOKENS: 25e3,
49685
- // Estimated characters per token (rough approximation)
49686
- CHARS_PER_TOKEN: 4,
49687
- // Maximum characters allowed (25000 * 4 = 100,000)
49688
- MAX_RESPONSE_CHARS: 1e5,
49689
- // Default pagination limits for different endpoints
49690
- DEFAULT_LIMITS: {
49691
- tokens: 100,
49692
- pools: 50,
49693
- trades: 100,
49694
- holders: 100,
49695
- events: 200,
49696
- nfts: 50
49697
- },
49698
- // Maximum allowed limits to prevent oversized responses
49699
- MAX_LIMITS: {
49700
- tokens: 100,
49701
- pools: 50,
49702
- trades: 200,
49703
- holders: 200,
49704
- events: 300,
49705
- nfts: 100
49706
- },
49707
- // Size thresholds for automatic truncation (in characters)
49708
- TRUNCATION_THRESHOLDS: {
49709
- warning: 8e4,
49710
- // Start warning at 80KB
49711
- truncate: 1e5
49712
- // Force truncation at 100KB
49713
- }
49714
- };
49715
-
49716
- // src/utils/responseHeadroom.ts
49717
- var MAX_RESULT_SIZE_META_KEY = "anthropic/maxResultSizeChars";
49718
- var ANNOTATED_MAX_RESULT_SIZE_CHARS = RESPONSE_LIMITS.MAX_RESPONSE_CHARS;
49719
- var DEFAULT_RESULT_SIZE_GUARD_CHARS = 2e4;
49720
- var HEADROOM_TOOL_PATTERNS = [
49721
- // Depth-of-book: order books, l2/l3 books.
49722
- /orderbook|order_book|l2_book|l3_orderbook/i,
49723
- // Holder / owner lists.
49724
- /holders?(?:_|$)|owners_for/i,
49725
- // OHLCV, candles, charts, and history/time-series families.
49726
- /ohlc|candle|kline|market_chart|chart|history|timeseries/i
49727
- ];
49728
- function toolNeedsResponseHeadroom(toolName) {
49729
- return HEADROOM_TOOL_PATTERNS.some((pattern) => pattern.test(toolName));
49730
- }
49731
- function getAnnotatedMaxResultSizeChars(tool96) {
49732
- const meta2 = tool96?._meta;
49733
- const value = meta2?.[MAX_RESULT_SIZE_META_KEY];
49734
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
49735
- }
49736
- function withResponseHeadroomMeta(tool96) {
49737
- if (!toolNeedsResponseHeadroom(tool96.name)) return tool96;
49738
- const existing = tool96._meta;
49739
- if (existing && existing[MAX_RESULT_SIZE_META_KEY] !== void 0) return tool96;
49740
- return {
49741
- ...tool96,
49742
- _meta: {
49743
- ...existing ?? {},
49744
- [MAX_RESULT_SIZE_META_KEY]: ANNOTATED_MAX_RESULT_SIZE_CHARS
49745
- }
49746
- };
49747
- }
49748
-
49749
49918
  // src/tools/polymarket/api.ts
49750
49919
  var DEFAULT_LIMIT3 = 10;
49751
49920
  var MAX_LIMIT3 = 50;
@@ -49753,9 +49922,14 @@ var MAX_OFFSET = 1e4;
49753
49922
  var DEFAULT_BOOK_DEPTH = 10;
49754
49923
  var MAX_BOOK_DEPTH = 50;
49755
49924
  var MAX_HISTORY_POINTS = 500;
49756
- var SEARCH_SCAN_LIMIT = 100;
49925
+ var SEARCH_EVENT_LIMIT = 20;
49757
49926
  var EVENT_MARKETS_PREVIEW = 10;
49758
- var EVENT_RESPONSE_BUDGET_CHARS = DEFAULT_RESULT_SIZE_GUARD_CHARS;
49927
+ var EVENT_RESPONSE_BUDGET_CHARS = (
49928
+ // Rows only: the okResult envelope (odds note, source URL twice, filters)
49929
+ // adds ~1k, and at the full 20k the guard still trimmed 48 rows to 45
49930
+ // (order=volume, limit=50, 2026-10-09).
49931
+ DEFAULT_RESULT_SIZE_GUARD_CHARS - 2e3
49932
+ );
49759
49933
  var DESCRIPTION_MAX_CHARS = 1200;
49760
49934
  var PRICE_INTERVALS = ["1d", "1w", "1m", "max"];
49761
49935
  var DEFAULT_FIDELITY_MINUTES = {
@@ -49955,6 +50129,21 @@ function parseInterval(value) {
49955
50129
  }
49956
50130
  return interval;
49957
50131
  }
50132
+ var EVENT_ORDERS = [
50133
+ "volume24hr",
50134
+ "volume",
50135
+ "liquidity",
50136
+ "endDate"
50137
+ ];
50138
+ function eventOrderParams(value, now = /* @__PURE__ */ new Date()) {
50139
+ const order = value === void 0 || value === null ? "volume24hr" : value;
50140
+ if (typeof order !== "string" || !EVENT_ORDERS.includes(order)) {
50141
+ invalid(
50142
+ `order must be one of ${EVENT_ORDERS.map((o) => `"${o}"`).join(", ")}; got ${JSON.stringify(value)}.`
50143
+ );
50144
+ }
50145
+ return order === "endDate" ? { order, ascending: true, end_date_min: now.toISOString() } : { order, ascending: false };
50146
+ }
49958
50147
  function parseFidelity(value, interval) {
49959
50148
  return parseBoundedInteger(
49960
50149
  value,
@@ -50283,6 +50472,22 @@ async function fetchGammaEvents(params) {
50283
50472
  const data = await fetchPolymarketJson(url, "Gamma /events");
50284
50473
  return { url, rows: asRecordArray(data, "Gamma /events") };
50285
50474
  }
50475
+ async function searchGammaMarkets(query, closed) {
50476
+ const url = buildUrl(GAMMA_BASE_URL, "/public-search", {
50477
+ q: query,
50478
+ limit_per_type: SEARCH_EVENT_LIMIT,
50479
+ events_status: closed ? "closed" : "active"
50480
+ });
50481
+ const data = asRecord(await fetchPolymarketJson(url, "Gamma /public-search"));
50482
+ const events = asRecordArray(data?.events ?? [], "Gamma /public-search");
50483
+ const rows = events.flatMap(
50484
+ (event) => (Array.isArray(event.markets) ? event.markets : []).map(asRecord).filter((market) => market !== null).map((market) => ({
50485
+ ...market,
50486
+ events: [{ slug: event.slug, title: event.title }]
50487
+ }))
50488
+ );
50489
+ return { url, rows, eventCount: events.length };
50490
+ }
50286
50491
  async function resolveMarket(selector) {
50287
50492
  if (selector.slug) {
50288
50493
  const { url, rows } = await fetchGammaMarkets({ slug: selector.slug });
@@ -50459,7 +50664,7 @@ function getAllPolymarketEndpoints() {
50459
50664
  },
50460
50665
  tool: {
50461
50666
  name: "polymarket_search_markets",
50462
- description: `Search Polymarket prediction markets by free text and list their market-implied odds, liquidity, volume, CLOB token ids, and end dates (Gamma API, keyless). With a query the tool scans the top ${SEARCH_SCAN_LIMIT} markets by 24h volume (honoring the active/closed filters) and keeps rows where every query term appears in the question, slug, bracket title, or event title. Without a query it returns the top markets by 24h volume.
50667
+ description: `Search Polymarket prediction markets by free text and list their market-implied odds, liquidity, volume, CLOB token ids, and end dates (Gamma API, keyless). With a query the tool runs Polymarket's own search across every event (up to ${SEARCH_EVENT_LIMIT} events, honoring the active/closed filters), keeps markets where every query term appears in the question, slug, bracket title, or event title, and orders them by 24h volume; when no market contains every term it returns Polymarket's relevance order instead and says so in scan.order. Without a query it returns the top markets by 24h volume.
50463
50668
 
50464
50669
  Args:
50465
50670
  - query (string, optional): free-text terms, all must match (case-insensitive), e.g. "fed rate" or "bitcoin 2026".
@@ -50470,7 +50675,7 @@ Args:
50470
50675
  Returns:
50471
50676
  ${ENVELOPE_DOC}
50472
50677
  "filters", "scan" { "scanned", "scan_limit", "matched", "order" }, and "rows": [{ "id", "slug", "question", "groupItemTitle", "conditionId", "eventSlug", "eventTitle", "outcomes", "outcomePrices" (0-1 per outcome, same order as outcomes), "clobTokenIds" (same order; pass to polymarket_get_orderbook / polymarket_get_price_history), "liquidity", "volume", "volume24hr", "endDate", "active", "closed" }].
50473
- may_have_more is true when more rows matched than were returned or the scan window was full; narrow the query or use polymarket_get_events with a tag.
50678
+ scan.scan_limit is the event cap for a query and the row limit without one. may_have_more is true when more rows matched than were returned or the search page was full; narrow the query or use polymarket_get_events with a tag.
50474
50679
 
50475
50680
  Examples:
50476
50681
  - { "query": "fed decision", "limit": 5 } \u2014 Fed-rate brackets with current odds.
@@ -50507,16 +50712,20 @@ Don't use when:
50507
50712
  const limit = parseLimit(args?.limit);
50508
50713
  const active = parseOptionalBoolean(args?.active, "active", true);
50509
50714
  const closed = parseOptionalBoolean(args?.closed, "closed", false);
50510
- const scanLimit = query ? SEARCH_SCAN_LIMIT : limit;
50511
- const { url, rows } = await fetchGammaMarkets({
50512
- limit: scanLimit,
50715
+ const scanLimit = query ? SEARCH_EVENT_LIMIT : limit;
50716
+ const search = query ? await searchGammaMarkets(query, closed) : null;
50717
+ const listing = search ?? await fetchGammaMarkets({
50718
+ limit,
50513
50719
  active,
50514
50720
  closed,
50515
50721
  order: "volume24hr",
50516
50722
  ascending: false
50517
50723
  });
50518
- const mapped = rows.map(toMarketRow2);
50519
- const matched = query ? mapped.filter(
50724
+ const { url, rows } = listing;
50725
+ const mapped = rows.map(toMarketRow2).filter(
50726
+ (row) => !query || row.active === active && row.closed === closed
50727
+ );
50728
+ const exact = query ? mapped.filter(
50520
50729
  (row) => matchesQuery(
50521
50730
  [
50522
50731
  row.question,
@@ -50527,19 +50736,22 @@ Don't use when:
50527
50736
  ],
50528
50737
  query
50529
50738
  )
50530
- ) : mapped;
50739
+ ).sort((a, b) => (b.volume24hr ?? 0) - (a.volume24hr ?? 0)) : mapped;
50740
+ const fuzzy = Boolean(query) && exact.length === 0;
50741
+ const matched = fuzzy ? mapped : exact;
50742
+ const scanned = search ? search.eventCount : rows.length;
50531
50743
  const items = matched.slice(0, limit);
50532
50744
  return okResult(url, {
50533
50745
  filters: { query, active, closed, limit },
50534
50746
  scan: {
50535
- scanned: rows.length,
50747
+ scanned,
50536
50748
  scan_limit: scanLimit,
50537
50749
  matched: matched.length,
50538
- order: "volume24hr desc"
50750
+ order: fuzzy ? "relevance (no market contains every term)" : "volume24hr desc"
50539
50751
  },
50540
50752
  rows: items,
50541
50753
  returnedCount: items.length,
50542
- may_have_more: matched.length > limit || rows.length >= scanLimit
50754
+ may_have_more: matched.length > limit || scanned >= scanLimit
50543
50755
  });
50544
50756
  } catch (error) {
50545
50757
  return errorResult3(toolName, error);
@@ -50561,14 +50773,16 @@ Args:
50561
50773
  - slug (string, optional): one event's URL slug (exact); other filters are ignored.
50562
50774
  - active (boolean, optional): Polymarket active flag filter (default true).
50563
50775
  - closed (boolean, optional): Polymarket closed flag filter (default false).
50776
+ - order (string, optional): "volume24hr" (default, trending: most traded in the last 24h), "volume" (biggest by lifetime volume), "liquidity" (deepest books), or "endDate" (closing soonest, from now on). Biggest first for the volume and liquidity sorts.
50564
50777
  ${PAGING_ARGS_DOC}
50565
50778
 
50566
50779
  Returns:
50567
50780
  ${ENVELOPE_DOC}
50568
- "filters", "markets_preview_limit" (nested previews actually kept per event, ${EVENT_MARKETS_PREVIEW} unless the size budget forced fewer), "rows_dropped_for_size" (0 unless headline data alone overran the budget), and "rows": [{ "id", "slug", "title", "active", "closed", "endDate", "liquidity", "volume", "volume24hr", "negRisk", "tags", "marketCount", "markets": [{ "slug", "question", "groupItemTitle", "conditionId", "outcomes", "outcomePrices", "clobTokenIds", "volume24hr" }], "marketsTruncated" }]. Rows are ordered by 24h volume (desc). returnedCount is always the number of rows actually in the response. Large limits are slow because Gamma nests every market of every event.
50781
+ "filters" (including the order applied), "markets_preview_limit" (nested previews actually kept per event, ${EVENT_MARKETS_PREVIEW} unless the size budget forced fewer), "rows_dropped_for_size" (0 unless headline data alone overran the budget), and "rows": [{ "id", "slug", "title", "active", "closed", "endDate", "liquidity", "volume", "volume24hr", "negRisk", "tags", "marketCount", "markets": [{ "slug", "question", "groupItemTitle", "conditionId", "outcomes", "outcomePrices", "clobTokenIds", "volume24hr" }], "marketsTruncated" }]. Rows follow the requested order (24h volume, desc, by default). returnedCount is always the number of rows actually in the response. Large limits are slow because Gamma nests every market of every event.
50569
50782
 
50570
50783
  Examples:
50571
50784
  - { "tag": "crypto", "limit": 5 } \u2014 the busiest crypto events.
50785
+ - { "order": "volume", "limit": 5 } \u2014 the biggest events by all-time volume (not just today's trend).
50572
50786
  - { "slug": "presidential-election-winner-2028" } \u2014 one event with its top-${EVENT_MARKETS_PREVIEW} candidates by odds.
50573
50787
  - { "limit": 10, "offset": 10 } \u2014 the next page of the most active events.
50574
50788
 
@@ -50593,6 +50807,11 @@ Don't use when:
50593
50807
  },
50594
50808
  active: ACTIVE_SCHEMA,
50595
50809
  closed: CLOSED_SCHEMA,
50810
+ order: {
50811
+ type: "string",
50812
+ enum: [...EVENT_ORDERS],
50813
+ description: 'Sort: "volume24hr" (default, trending), "volume" (biggest all-time), "liquidity", or "endDate" (closing soonest from now).'
50814
+ },
50596
50815
  limit: LIMIT_SCHEMA,
50597
50816
  offset: OFFSET_SCHEMA
50598
50817
  }
@@ -50608,19 +50827,19 @@ Don't use when:
50608
50827
  const offset = parseOffset(args?.offset);
50609
50828
  const active = parseOptionalBoolean(args?.active, "active", true);
50610
50829
  const closed = parseOptionalBoolean(args?.closed, "closed", false);
50830
+ const sort = eventOrderParams(args?.order);
50611
50831
  const { url, rows } = slug ? await fetchGammaEvents({ slug }) : await fetchGammaEvents({
50612
50832
  limit,
50613
50833
  offset,
50614
50834
  active,
50615
50835
  closed,
50616
50836
  tag_slug: tag ?? void 0,
50617
- order: "volume24hr",
50618
- ascending: false
50837
+ ...sort
50619
50838
  });
50620
50839
  const bounded = boundEventRows(rows);
50621
50840
  const items = bounded.rows;
50622
50841
  return okResult(url, {
50623
- filters: slug ? { slug } : { tag, active, closed, limit, offset },
50842
+ filters: slug ? { slug } : { tag, active, closed, order: sort.order, limit, offset },
50624
50843
  markets_preview_limit: bounded.marketsPreviewLimit,
50625
50844
  rows_dropped_for_size: bounded.rowsDropped,
50626
50845
  rows: items,
@@ -51032,7 +51251,7 @@ function isAbortError(error) {
51032
51251
  const name = error?.name;
51033
51252
  return name === "AbortError" || name === "TimeoutError";
51034
51253
  }
51035
- async function fetchJsonWithTimeout(url, label, timeoutMs = SIGNALS_FETCH_TIMEOUT_MS) {
51254
+ async function fetchJsonWithTimeout(url, label, timeoutMs = SIGNALS_FETCH_TIMEOUT_MS, headers = {}) {
51036
51255
  const controller = new AbortController();
51037
51256
  const timer = setTimeout(() => controller.abort(), timeoutMs);
51038
51257
  try {
@@ -51042,7 +51261,8 @@ async function fetchJsonWithTimeout(url, label, timeoutMs = SIGNALS_FETCH_TIMEOU
51042
51261
  signal: controller.signal,
51043
51262
  headers: {
51044
51263
  Accept: "application/json",
51045
- "User-Agent": "hive-mcp-server/1.0.0"
51264
+ "User-Agent": "hive-mcp-server/1.0.0",
51265
+ ...headers
51046
51266
  }
51047
51267
  });
51048
51268
  } catch (error) {
@@ -52287,6 +52507,334 @@ Don't use when:
52287
52507
  ];
52288
52508
  }
52289
52509
 
52510
+ // src/tools/blockscout/tools.ts
52511
+ import Coingecko2 from "@coingecko/coingecko-typescript";
52512
+ var BLOCKSCOUT_PROVIDER = "Blockscout";
52513
+ var HOLDERS_PAGE_SIZE = 50;
52514
+ var DEFAULT_MAX_PAGES = 20;
52515
+ var MAX_PAGES = 40;
52516
+ var TIME_BUDGET_MS = 2e4;
52517
+ var CHAINS = {
52518
+ eth: { chainId: 1, host: "eth.blockscout.com", gecko: "eth" },
52519
+ base: { chainId: 8453, host: "base.blockscout.com", gecko: "base" },
52520
+ optimism: { chainId: 10, host: "explorer.optimism.io", gecko: "optimism" },
52521
+ arbitrum: {
52522
+ chainId: 42161,
52523
+ host: "arbitrum.blockscout.com",
52524
+ gecko: "arbitrum"
52525
+ },
52526
+ polygon: {
52527
+ chainId: 137,
52528
+ host: "polygon.blockscout.com",
52529
+ gecko: "polygon_pos"
52530
+ }
52531
+ };
52532
+ var CHAIN_ALIASES = {
52533
+ ethereum: "eth",
52534
+ mainnet: "eth",
52535
+ "1": "eth",
52536
+ "8453": "base",
52537
+ op: "optimism",
52538
+ "10": "optimism",
52539
+ arb: "arbitrum",
52540
+ "42161": "arbitrum",
52541
+ matic: "polygon",
52542
+ polygon_pos: "polygon",
52543
+ "137": "polygon"
52544
+ };
52545
+ var USD_TIERS = [
52546
+ { label: ">$10k", min_usd: 1e4, max_usd: null },
52547
+ { label: "$1k-$10k", min_usd: 1e3, max_usd: 1e4 },
52548
+ { label: "$100-$1k", min_usd: 100, max_usd: 1e3 },
52549
+ { label: "$1-$100", min_usd: 1, max_usd: 100 },
52550
+ { label: "<$1", min_usd: 0, max_usd: 1 }
52551
+ ];
52552
+ function tallyTiers(rows, totalHolders, stop) {
52553
+ const reachedBottom = stop === "end_of_holders";
52554
+ const smallest = rows.length > 0 ? rows[rows.length - 1].usd : null;
52555
+ const tiers = USD_TIERS.map((tier) => {
52556
+ const members = rows.filter(
52557
+ (row) => row.usd >= tier.min_usd && (tier.max_usd === null || row.usd < tier.max_usd)
52558
+ );
52559
+ const exact = reachedBottom || smallest !== null && smallest < tier.min_usd;
52560
+ const reached = smallest !== null && smallest < (tier.max_usd ?? Infinity);
52561
+ return {
52562
+ ...tier,
52563
+ count: exact || reached ? members.length : null,
52564
+ contracts: exact || reached ? members.filter((r) => r.is_contract).length : null,
52565
+ exact,
52566
+ lower_bound: !exact && reached
52567
+ };
52568
+ });
52569
+ const dust = tiers[tiers.length - 1];
52570
+ if (!dust.exact && stop === "below_smallest_tier" && totalHolders !== null) {
52571
+ const above = rows.filter((row) => row.usd >= 1).length;
52572
+ dust.count = Math.max(totalHolders - above, 0);
52573
+ dust.contracts = null;
52574
+ dust.exact = true;
52575
+ dust.lower_bound = false;
52576
+ }
52577
+ const real = tiers.filter((tier) => tier.min_usd >= 100);
52578
+ return {
52579
+ tiers,
52580
+ holders_100_usd_plus: {
52581
+ count: real.reduce((sum, tier) => sum + (tier.count ?? 0), 0),
52582
+ exact: real.every((tier) => tier.exact)
52583
+ },
52584
+ exhaustive: tiers.every((tier) => tier.exact),
52585
+ smallest_scanned_usd: smallest
52586
+ };
52587
+ }
52588
+ function parseArgs(args) {
52589
+ const rawNetwork = String(args?.network ?? args?.chain ?? "").trim().toLowerCase();
52590
+ const key = CHAIN_ALIASES[rawNetwork] ?? rawNetwork;
52591
+ const chain = CHAINS[key];
52592
+ if (!chain) {
52593
+ throw new SignalsInputError(
52594
+ `network must be one of ${Object.keys(CHAINS).join(", ")} (or a chain id); got ${JSON.stringify(args?.network ?? null)}.`
52595
+ );
52596
+ }
52597
+ const address = String(args?.address ?? "").trim();
52598
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
52599
+ throw new SignalsInputError(
52600
+ `address must be a 0x token contract address; got ${JSON.stringify(args?.address ?? null)}.`
52601
+ );
52602
+ }
52603
+ const priceOverride = args?.price_usd === void 0 || args?.price_usd === null ? null : parseNumberArg(args.price_usd, "price_usd", Number.MIN_VALUE, 1e12, 0);
52604
+ return {
52605
+ network: key,
52606
+ chain,
52607
+ address,
52608
+ maxPages: parseIntegerArg(
52609
+ args?.max_pages,
52610
+ "max_pages",
52611
+ 1,
52612
+ MAX_PAGES,
52613
+ DEFAULT_MAX_PAGES
52614
+ ),
52615
+ priceOverride
52616
+ };
52617
+ }
52618
+ function blockscoutGet(chain, path) {
52619
+ const apiKey = getEnvVar("BLOCKSCOUT_API_KEY");
52620
+ const url = apiKey ? `https://api.blockscout.com/${chain.chainId}/api/v2${path}` : `https://${chain.host}/api/v2${path}`;
52621
+ return fetchJsonWithTimeout(
52622
+ url,
52623
+ `Blockscout (${chain.chainId})`,
52624
+ void 0,
52625
+ apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
52626
+ ).catch((error) => {
52627
+ if (error instanceof SignalsHttpError && error.status === 404) {
52628
+ throw new SignalsInputError(
52629
+ `Blockscout has no token at this address on chain ${chain.chainId}.`
52630
+ );
52631
+ }
52632
+ if (!apiKey && error instanceof SignalsHttpError && (error.status === 403 || error.status === 402)) {
52633
+ throw new Error(
52634
+ `Blockscout's public explorer for chain ${chain.chainId} refused the request (HTTP ${error.status}, bot protection). Set BLOCKSCOUT_API_KEY (free API key at dev.blockscout.com) to use the Blockscout PRO API.`
52635
+ );
52636
+ }
52637
+ throw error;
52638
+ });
52639
+ }
52640
+ var ownCoinGecko = null;
52641
+ async function resolvePrice(chain, address, token) {
52642
+ const blockscoutRate = parseFiniteNumber3(token.exchange_rate);
52643
+ if (blockscoutRate !== null && blockscoutRate > 0) {
52644
+ return { price: blockscoutRate, source: "blockscout_exchange_rate" };
52645
+ }
52646
+ try {
52647
+ if (!ownCoinGecko)
52648
+ ownCoinGecko = new Coingecko2(getCoinGeckoClientOptions());
52649
+ const result2 = await ownCoinGecko.onchain.simple.networks.tokenPrice.getAddresses(
52650
+ address,
52651
+ { network: chain.gecko }
52652
+ );
52653
+ const prices = result2?.data?.attributes?.token_prices ?? {};
52654
+ const price = parseFiniteNumber3(
52655
+ prices[address.toLowerCase()] ?? prices[address]
52656
+ );
52657
+ if (price !== null && price > 0) {
52658
+ return { price, source: "coingecko_onchain_price" };
52659
+ }
52660
+ } catch {
52661
+ }
52662
+ return { price: null, source: null };
52663
+ }
52664
+ async function getTokenHolderDistribution(args, now = Date.now) {
52665
+ const { network, chain, address, maxPages, priceOverride } = parseArgs(args);
52666
+ const started = now();
52667
+ const token = await blockscoutGet(chain, `/tokens/${address}`) ?? {};
52668
+ if (token.type !== void 0 && token.type !== "ERC-20") {
52669
+ throw new SignalsInputError(
52670
+ `this address is a ${String(token.type)} token; holder tiers are computed for ERC-20 tokens only.`
52671
+ );
52672
+ }
52673
+ const decimals = parseFiniteNumber3(token.decimals);
52674
+ if (decimals === null) {
52675
+ throw new Error("Blockscout returned no decimals for this token");
52676
+ }
52677
+ const totalHolders = parseFiniteNumber3(token.holders_count ?? token.holders);
52678
+ const { price, source: priceSource } = priceOverride !== null ? { price: priceOverride, source: "caller" } : await resolvePrice(chain, address, token);
52679
+ const base = {
52680
+ provider: BLOCKSCOUT_PROVIDER,
52681
+ network,
52682
+ chain_id: chain.chainId,
52683
+ address,
52684
+ token: {
52685
+ name: token.name ?? null,
52686
+ symbol: token.symbol ?? null,
52687
+ decimals,
52688
+ total_supply: token.total_supply ?? null
52689
+ },
52690
+ total_holders: totalHolders,
52691
+ price_usd: price,
52692
+ price_source: priceSource
52693
+ };
52694
+ if (price === null) {
52695
+ return {
52696
+ ...base,
52697
+ runtime_status: "degraded",
52698
+ fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
52699
+ tiers: null,
52700
+ note: "No USD price was found for this token, so holders could not be tiered by value. total_holders counts every address with a positive balance, dust included. Pass price_usd to tier anyway."
52701
+ };
52702
+ }
52703
+ const rows = [];
52704
+ let pages = 0;
52705
+ let cursor = "";
52706
+ let stop = "end_of_holders";
52707
+ for (; ; ) {
52708
+ if (pages >= maxPages) {
52709
+ stop = "max_pages";
52710
+ break;
52711
+ }
52712
+ if (now() - started > TIME_BUDGET_MS) {
52713
+ stop = "time_budget";
52714
+ break;
52715
+ }
52716
+ const page = await blockscoutGet(
52717
+ chain,
52718
+ `/tokens/${address}/holders${cursor}`
52719
+ ) ?? {};
52720
+ pages += 1;
52721
+ const items = Array.isArray(page.items) ? page.items : [];
52722
+ let belowFloor = false;
52723
+ for (const item of items) {
52724
+ const holder = item ?? {};
52725
+ const units = parseFiniteNumber3(holder.value);
52726
+ if (units === null) continue;
52727
+ const usd2 = units / 10 ** decimals * price;
52728
+ rows.push({ usd: usd2, is_contract: holder.address?.is_contract === true });
52729
+ if (usd2 < 1) {
52730
+ belowFloor = true;
52731
+ break;
52732
+ }
52733
+ }
52734
+ if (belowFloor) {
52735
+ stop = "below_smallest_tier";
52736
+ break;
52737
+ }
52738
+ const next = page.next_page_params;
52739
+ if (!next || typeof next !== "object" || items.length === 0) break;
52740
+ cursor = `?${new URLSearchParams(
52741
+ Object.entries(next).map(([k, v]) => [
52742
+ k,
52743
+ String(v)
52744
+ ])
52745
+ ).toString()}`;
52746
+ }
52747
+ const tally = tallyTiers(rows, totalHolders, stop);
52748
+ return {
52749
+ ...base,
52750
+ runtime_status: "ok",
52751
+ fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
52752
+ holders_scanned: rows.length,
52753
+ pages_fetched: pages,
52754
+ stopped_reason: stop,
52755
+ ...tally,
52756
+ lower_bound: !tally.exhaustive,
52757
+ note: (tally.exhaustive ? "Every tier is exact." : `The walk stopped (${stop}) at a holder worth $${tally.smallest_scanned_usd?.toFixed(2)}; tiers marked exact are complete, lower_bound tiers are at least the count shown, and null tiers were not reached. Raise max_pages (up to ${MAX_PAGES}) to go deeper.`) + " Counts include contracts (pools, vesting, treasuries); each tier's contracts field says how many. USD values use price_usd at fetch time."
52758
+ };
52759
+ }
52760
+ function getAllBlockscoutEndpoints() {
52761
+ return [
52762
+ {
52763
+ metadata: {
52764
+ resource: "blockscout.tokens.holders",
52765
+ operation: "read",
52766
+ tags: ["blockscout", "tokens", "holders", "distribution", "onchain"]
52767
+ },
52768
+ tool: {
52769
+ name: "get_token_holder_distribution",
52770
+ description: `Count a token's holders by USD balance: total holders plus how many hold >$10k, $1k-$10k, $100-$1k, $1-$100, and under $1 (dust), from Blockscout's holder list. Answers "how many real (non-dust) holders does this token have?". Keyless on EVM chains: ${Object.keys(CHAINS).join(", ")}.
52771
+
52772
+ Args:
52773
+ - network (string, required): ${Object.keys(CHAINS).join(", ")}, or a chain id (1, 8453, 10, 42161, 137).
52774
+ - address (string, required): ERC-20 token contract address.
52775
+ - max_pages (integer, optional): holder pages of ${HOLDERS_PAGE_SIZE} to walk, 1-${MAX_PAGES} (default ${DEFAULT_MAX_PAGES}). The walk also stops after ${TIME_BUDGET_MS / 1e3} s, at the end of the list, or at the first holder under $1.
52776
+ - price_usd (number, optional): USD price to tier with. Default: Blockscout's exchange rate, then the GeckoTerminal on-chain price.
52777
+
52778
+ Returns:
52779
+ { "provider": "Blockscout", "runtime_status", "fetched_at", "network", "chain_id", "address", "token": { "name", "symbol", "decimals", "total_supply" }, "total_holders", "price_usd", "price_source", "holders_scanned", "pages_fetched", "stopped_reason", "tiers": [{ "label", "min_usd", "max_usd", "count", "contracts", "exact", "lower_bound" }], "holders_100_usd_plus": { "count", "exact" }, "exhaustive", "lower_bound", "smallest_scanned_usd", "note" }. A tier with exact=false and lower_bound=true holds at least count holders; count null means the walk never reached it.
52780
+
52781
+ Examples:
52782
+ - { "network": "base", "address": "0x91dA780BC7f4B7Cf19ABE90411a2a296Ec5FF787" }
52783
+ - { "network": "eth", "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "max_pages": 5 }
52784
+
52785
+ Use when:
52786
+ - You need holder counts above a USD size, or how much of the holder count is dust.
52787
+ Don't use when:
52788
+ - You need the largest holders and their share of supply (use get_token_top_holders).`,
52789
+ inputSchema: {
52790
+ type: "object",
52791
+ properties: {
52792
+ network: {
52793
+ type: "string",
52794
+ description: `Chain: ${Object.keys(CHAINS).join(", ")}, or a chain id.`
52795
+ },
52796
+ address: {
52797
+ type: "string",
52798
+ description: "ERC-20 token contract address (0x...)."
52799
+ },
52800
+ max_pages: {
52801
+ type: "integer",
52802
+ minimum: 1,
52803
+ maximum: MAX_PAGES,
52804
+ default: DEFAULT_MAX_PAGES,
52805
+ description: `Holder pages of ${HOLDERS_PAGE_SIZE} to walk (1-${MAX_PAGES}).`
52806
+ },
52807
+ price_usd: {
52808
+ type: "number",
52809
+ exclusiveMinimum: 0,
52810
+ description: "USD price to tier with; default Blockscout's rate, then GeckoTerminal."
52811
+ }
52812
+ },
52813
+ required: ["network", "address"],
52814
+ additionalProperties: false
52815
+ },
52816
+ annotations: VOLATILE_READ_ANNOTATIONS
52817
+ },
52818
+ handler: async (_client, args) => {
52819
+ try {
52820
+ return asTextContentResult(await getTokenHolderDistribution(args));
52821
+ } catch (error) {
52822
+ const message = error instanceof Error ? error.message : String(error);
52823
+ return asTextContentResult(
52824
+ {
52825
+ error: message,
52826
+ runtime_status: error instanceof SignalsInputError ? "invalid_input" : classifyRuntimeStatusFromError(message),
52827
+ tool: "get_token_holder_distribution",
52828
+ provider: BLOCKSCOUT_PROVIDER
52829
+ },
52830
+ true
52831
+ );
52832
+ }
52833
+ }
52834
+ }
52835
+ ];
52836
+ }
52837
+
52290
52838
  // src/freeWriteEndpoints.ts
52291
52839
  var FREE_WRITE_ENDPOINTS = /* @__PURE__ */ new Set([
52292
52840
  // Feedback is a bounded, rate-limited insert into Hive's own table. It
@@ -52856,7 +53404,7 @@ Don't use when:
52856
53404
  }
52857
53405
 
52858
53406
  // src/wrapper/index.ts
52859
- import Coingecko2 from "@coingecko/coingecko-typescript";
53407
+ import Coingecko3 from "@coingecko/coingecko-typescript";
52860
53408
 
52861
53409
  // src/utils/descriptionGenerator.ts
52862
53410
  function needsEnrichment(description) {
@@ -53627,6 +54175,11 @@ var CHECK_TOKEN_SAFETY_OUTPUT_SCHEMA = {
53627
54175
  additionalProperties: true,
53628
54176
  description: "Raw provider risk datapoints; '1' means flagged. A missing key means the provider did not report that signal, not that it passed."
53629
54177
  },
54178
+ summary: {
54179
+ type: "object",
54180
+ additionalProperties: true,
54181
+ description: "Concise scam signals: verdicts (plain-language list), lp (lock state and top LP holder), top_holders (largest holder and top-10 share, DEX pairs excluded), creator, owner. Percents are 0-100."
54182
+ },
53630
54183
  holder_count: {},
53631
54184
  token_name: { type: "string" },
53632
54185
  note: { type: "string" }
@@ -54132,7 +54685,7 @@ var PROVIDER_CATALOG = [
54132
54685
  id: "archive",
54133
54686
  name: "Hive Archive",
54134
54687
  prefix: "archive_",
54135
- type: "Cross-venue crypto derivatives history since 2023-11 (33 CEX+DEX venues): funding-rate settlements, open interest, basis, long/short ratio, and liquidations from Hive's own archive store",
54688
+ 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",
54136
54689
  clientStrategy: "internal",
54137
54690
  credentialRequirement: {
54138
54691
  label: "SHARPE_ARCHIVE_SUPABASE_URL and SHARPE_ARCHIVE_SUPABASE_SERVICE_ROLE_KEY",
@@ -54170,6 +54723,15 @@ var PROVIDER_CATALOG = [
54170
54723
  alwaysAvailable: true,
54171
54724
  matches: (_toolName, metadata100) => metadata100.tags.includes("blockchain-com")
54172
54725
  },
54726
+ {
54727
+ id: "blockscout",
54728
+ name: "Blockscout",
54729
+ prefix: "blockscout_",
54730
+ type: "EVM token holder lists and counts from Blockscout explorers (keyless public API; BLOCKSCOUT_API_KEY switches to the PRO API)",
54731
+ clientStrategy: "internal",
54732
+ alwaysAvailable: true,
54733
+ matches: (_toolName, metadata100) => metadata100.tags.includes("blockscout")
54734
+ },
54173
54735
  {
54174
54736
  id: "fetch",
54175
54737
  name: "Open Data Fetch",
@@ -54407,7 +54969,9 @@ var HELIUS_WALLET_PLAN = "because Helius answers 403 for it on Hive's current He
54407
54969
  var DEFILLAMA_PRO = "because it needs a DeFiLlama Pro plan, which Hive does not have";
54408
54970
  var DEFILLAMA_DERIVATIVES_REPLACEMENT = "Use defillama_get_open_interest, defillama_get_derivatives_aggregator_volumes, or get_derivatives_exchanges for derivatives activity.";
54409
54971
  var ALCHEMY_PAYG = "because Alchemy offers it only on Pay As You Go and Enterprise plans, and Hive's key is on the Free tier";
54410
- var ALCHEMY_BUNDLE_REPLACEMENT = "Use tenderly_simulate_bundle for multi-transaction simulation, or alchemy_simulate_asset_changes and alchemy_simulate_execution one transaction at a time.";
54972
+ var ALCHEMY_BUNDLE_REPLACEMENT = "Use tenderly_simulate_bundle or tenderly_node_simulate_bundle for multi-transaction simulation.";
54973
+ var ALCHEMY_SIMULATION_SUNSET = 'because Alchemy shut down its Transaction Simulation API on 2026-09-30 and now answers "Unsupported method" on every plan';
54974
+ var ALCHEMY_SIMULATION_REPLACEMENT = "Use tenderly_simulate_transaction or tenderly_node_simulate_transaction; both return the execution trace, logs, and asset and balance changes without broadcasting.";
54411
54975
  var CCXT_PRIVATE = "because every exchange that serves it requires an authenticated exchange account";
54412
54976
  var CCXT_BORROW_REPLACEMENT = "Use ccxt_fetch_borrow_rate_history or ccxt_fetch_borrow_rate_histories (public) and read the latest point.";
54413
54977
  var UNSERVABLE_TOOLS = {
@@ -54491,6 +55055,21 @@ var UNSERVABLE_TOOLS = {
54491
55055
  reason: CCXT_PRIVATE,
54492
55056
  replacement: CCXT_BORROW_REPLACEMENT
54493
55057
  },
55058
+ alchemy_simulate_asset_changes: {
55059
+ reason: ALCHEMY_SIMULATION_SUNSET,
55060
+ replacement: ALCHEMY_SIMULATION_REPLACEMENT,
55061
+ date: "2026-10-09"
55062
+ },
55063
+ alchemy_simulate_execution: {
55064
+ reason: ALCHEMY_SIMULATION_SUNSET,
55065
+ replacement: ALCHEMY_SIMULATION_REPLACEMENT,
55066
+ date: "2026-10-09"
55067
+ },
55068
+ alchemy_is_holder_of_contract: {
55069
+ reason: 'because Alchemy deprecated the isHolderOfContract endpoint, which now answers 404 "This API endpoint has been deprecated"',
55070
+ replacement: "Use alchemy_get_nfts_for_owner for the wallet and look for the contract, or alchemy_get_owners_for_contract and look for the wallet.",
55071
+ date: "2026-10-09"
55072
+ },
54494
55073
  goplus_simulate_solana_transaction: {
54495
55074
  reason: 'because GoPlus answers "system error" to every Solana pre-execution request, including a fresh valid transfer',
54496
55075
  replacement: "Use get_solana_token_security for the tokens the transaction touches; Hive has no other Solana pre-signing simulation."
@@ -54503,7 +55082,7 @@ function getRetiredTool(toolName) {
54503
55082
  if (isUnservableTool(toolName)) {
54504
55083
  const entry2 = UNSERVABLE_TOOLS[toolName];
54505
55084
  return {
54506
- date: UNSERVABLE_TOOLS_DATE,
55085
+ date: entry2.date ?? UNSERVABLE_TOOLS_DATE,
54507
55086
  cause: entry2.reason,
54508
55087
  replacement: entry2.replacement
54509
55088
  };
@@ -54680,7 +55259,40 @@ function boundedJsonTruncationStub(originalLength, note, characterLimit) {
54680
55259
  ];
54681
55260
  return candidates.find((candidate) => candidate.length <= characterLimit) ?? "";
54682
55261
  }
54683
- function smartTruncate(text, characterLimit = CHARACTER_LIMIT) {
55262
+ function inputSchemaParamNames(schema) {
55263
+ const properties = schema?.properties;
55264
+ return properties && typeof properties === "object" ? Object.keys(properties) : [];
55265
+ }
55266
+ function truncationRecallHint(params = [], nextOffset) {
55267
+ const names = new Set(params);
55268
+ if (names.has("offset")) {
55269
+ return `Re-call with offset=${nextOffset ?? "_pagination.next_offset"} to continue, or add filters.`;
55270
+ }
55271
+ const sizeParam = ["limit", "per_page", "page_size", "count"].find(
55272
+ (name) => names.has(name)
55273
+ );
55274
+ return sizeParam ? `This tool has no offset parameter: a smaller ${sizeParam} returns an untruncated page; narrower filters reach the remaining rows.` : "This tool has no paging parameter: narrow the request with its filters to see the rest.";
55275
+ }
55276
+ function truncationPagination(kept, original, context) {
55277
+ const params = [...context.params ?? []];
55278
+ const head = `Response truncated from ${original} to ${kept} items to fit the response budget.`;
55279
+ const base = {
55280
+ count: kept,
55281
+ total_count: original,
55282
+ has_more: kept < original
55283
+ };
55284
+ if (!params.includes("offset")) {
55285
+ return { ...base, note: `${head} ${truncationRecallHint(params)}` };
55286
+ }
55287
+ const start = typeof context.offset === "number" && Number.isFinite(context.offset) ? Math.max(0, Math.floor(context.offset)) : 0;
55288
+ return {
55289
+ ...base,
55290
+ offset: start,
55291
+ next_offset: start + kept,
55292
+ note: `${head} ${truncationRecallHint(params, start + kept)}`
55293
+ };
55294
+ }
55295
+ function smartTruncate(text, characterLimit = CHARACTER_LIMIT, pagination = {}) {
54684
55296
  if (text.length <= characterLimit) return text;
54685
55297
  const originalLength = text.length;
54686
55298
  const head = text.trimStart();
@@ -54741,14 +55353,11 @@ function smartTruncate(text, characterLimit = CHARACTER_LIMIT) {
54741
55353
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
54742
55354
  const obj = parsed;
54743
55355
  if (pagInfo && pagInfo.kept < pagInfo.original) {
54744
- obj._pagination = {
54745
- count: pagInfo.kept,
54746
- total_count: pagInfo.original,
54747
- offset: 0,
54748
- has_more: pagInfo.kept < pagInfo.original,
54749
- next_offset: pagInfo.kept,
54750
- note: `Response truncated from ${pagInfo.original} to ${pagInfo.kept} items. Re-call with offset=${pagInfo.kept} or add filters to see more.`
54751
- };
55356
+ obj._pagination = truncationPagination(
55357
+ pagInfo.kept,
55358
+ pagInfo.original,
55359
+ pagination
55360
+ );
54752
55361
  }
54753
55362
  if (dropped.length > 0) {
54754
55363
  obj._dropped_fields = dropped;
@@ -54865,14 +55474,15 @@ function stripProviderPrefix(toolName) {
54865
55474
  var fallbackCoinGeckoClient = null;
54866
55475
  function getFallbackCoinGeckoClient() {
54867
55476
  if (!fallbackCoinGeckoClient) {
54868
- fallbackCoinGeckoClient = new Coingecko2(getCoinGeckoClientOptions());
55477
+ fallbackCoinGeckoClient = new Coingecko3(getCoinGeckoClientOptions());
54869
55478
  }
54870
55479
  return fallbackCoinGeckoClient;
54871
55480
  }
54872
55481
  function isKeylessCoinGeckoClient(client2) {
54873
- return client2 instanceof Coingecko2 && !client2.proAPIKey && !client2.demoAPIKey;
55482
+ return client2 instanceof Coingecko3 && !client2.proAPIKey && !client2.demoAPIKey;
54874
55483
  }
54875
- function createSafeHandler(handler96, metadata100, toolName) {
55484
+ function createSafeHandler(handler96, metadata100, toolName, inputSchema) {
55485
+ const schemaParams = inputSchemaParamNames(inputSchema);
54876
55486
  const credentialRequirement = getCredentialRequirementForTool(
54877
55487
  toolName,
54878
55488
  metadata100
@@ -54968,7 +55578,15 @@ function createSafeHandler(handler96, metadata100, toolName) {
54968
55578
  withExecMarker(
54969
55579
  {
54970
55580
  ...result2,
54971
- content: [{ type: "text", text: smartTruncate(text) }]
55581
+ content: [
55582
+ {
55583
+ type: "text",
55584
+ text: smartTruncate(text, CHARACTER_LIMIT, {
55585
+ params: schemaParams,
55586
+ offset: safeArgs.offset
55587
+ })
55588
+ }
55589
+ ]
54972
55590
  },
54973
55591
  {
54974
55592
  truncated: true,
@@ -55131,6 +55749,7 @@ function getAllWrappedEndpoints() {
55131
55749
  ...getAllArchiveEndpoints(),
55132
55750
  ...getAllPolymarketEndpoints(),
55133
55751
  ...getAllSignalsEndpoints(),
55752
+ ...getAllBlockscoutEndpoints(),
55134
55753
  ...getAllFetchEndpoints()
55135
55754
  ];
55136
55755
  const allEndpointsJSON = wrapper;
@@ -55186,7 +55805,8 @@ function getAllWrappedEndpoints() {
55186
55805
  handler: createSafeHandler(
55187
55806
  endpoint.handler,
55188
55807
  endpoint.metadata,
55189
- toolInstanse.name
55808
+ toolInstanse.name,
55809
+ toolInstanse.inputSchema
55190
55810
  ),
55191
55811
  metadata: endpoint.metadata,
55192
55812
  tool: toolInstanse
@@ -55230,7 +55850,8 @@ function getAllWrappedEndpoints() {
55230
55850
  handler: createSafeHandler(
55231
55851
  endpoint.handler,
55232
55852
  endpoint.metadata,
55233
- toolInstanse.name
55853
+ toolInstanse.name,
55854
+ toolInstanse.inputSchema
55234
55855
  ),
55235
55856
  metadata: endpoint.metadata,
55236
55857
  tool: toolInstanse
@@ -55266,7 +55887,7 @@ var ToolRegistry = [
55266
55887
  {
55267
55888
  category: "Market Data and Price",
55268
55889
  name: "get_market_and_price_endpoints",
55269
- description: "Endpoints to retrieve real-time and historical cryptocurrency prices, market caps, trading volumes, OHLC data, global market statistics, supported currencies, basic market performance metrics across different timeframes and asset platforms, stablecoin market analytics, stablecoin price tracking, comprehensive stablecoin market cap data across multiple chains, centralized exchange (CEX) trading data including real-time tickers, order books, recent trades, candlestick charts, best bid/ask prices, CEX exchange metadata and volume charts, derivatives pricing (index, mark, and premium), perpetual futures funding rates, leverage tiers, options trading analytics, and available trading markets/currencies across major exchanges. Provider guide: Use coingecko tools for market-wide price data. Use ccxt_ tools for CEX trading data. Use alchemy_ tools for supported token price reads and EVM gas data. Use archive_ tools for cross-venue derivatives HISTORY (funding settlements, open interest, basis, long/short ratio, liquidations) since 2023-11.",
55890
+ description: "Endpoints to retrieve real-time and historical cryptocurrency prices, market caps, trading volumes, OHLC data, global market statistics, supported currencies, basic market performance metrics across different timeframes and asset platforms, stablecoin market analytics, stablecoin price tracking, comprehensive stablecoin market cap data across multiple chains, centralized exchange (CEX) trading data including real-time tickers, order books, recent trades, candlestick charts, best bid/ask prices, CEX exchange metadata and volume charts, derivatives pricing (index, mark, and premium), perpetual futures funding rates, leverage tiers, options trading analytics, and available trading markets/currencies across major exchanges. Provider guide: Use coingecko tools for market-wide price data. Use ccxt_ tools for CEX trading data. Use alchemy_ tools for supported token price reads and EVM gas data. Use archive_ tools for cross-venue derivatives HISTORY: funding settlements since 2023-11; open interest, basis, long/short ratio and liquidations over shorter windows (archive_get_coverage gives exact depth).",
55270
55891
  tools: [
55271
55892
  "get_coins_index",
55272
55893
  "get_coins_market_data",
@@ -55587,6 +56208,7 @@ var ToolRegistry = [
55587
56208
  "get_tokens_by_address",
55588
56209
  "get_token_info",
55589
56210
  "get_token_top_holders",
56211
+ "get_token_holder_distribution",
55590
56212
  "get_token_holders_chart",
55591
56213
  "get_multi_tokens",
55592
56214
  "get_recently_updated_tokens",
@@ -55689,7 +56311,6 @@ var ToolRegistry = [
55689
56311
  "alchemy_get_floor_price",
55690
56312
  "alchemy_get_contracts_for_owner",
55691
56313
  "alchemy_is_spam_contract",
55692
- "alchemy_is_holder_of_contract",
55693
56314
  "alchemy_solana_get_asset",
55694
56315
  "alchemy_solana_get_asset_proof",
55695
56316
  "alchemy_solana_get_asset_proofs",
@@ -55791,9 +56412,7 @@ var ToolRegistry = [
55791
56412
  "tenderly_get_contract_abi",
55792
56413
  "tenderly_get_storage_changes",
55793
56414
  "tenderly_get_transactions_range",
55794
- "alchemy_get_token_allowance",
55795
- "alchemy_simulate_asset_changes",
55796
- "alchemy_simulate_execution"
56415
+ "alchemy_get_token_allowance"
55797
56416
  ]
55798
56417
  },
55799
56418
  {
@@ -55966,7 +56585,7 @@ import {
55966
56585
  import { existsSync, readFileSync, readdirSync } from "fs";
55967
56586
  import { dirname, resolve as resolvePath } from "path";
55968
56587
  import { fileURLToPath } from "url";
55969
- import Coingecko4 from "@coingecko/coingecko-typescript";
56588
+ import Coingecko5 from "@coingecko/coingecko-typescript";
55970
56589
 
55971
56590
  // src/tools/dynamic-tools.ts
55972
56591
  import { z as z4 } from "zod";
@@ -56282,20 +56901,51 @@ function isUnmarkedErrorResponse(result2) {
56282
56901
  }
56283
56902
  return false;
56284
56903
  }
56904
+ var PROVIDER_SIDE_ERROR_COPY = {
56905
+ plan_required: {
56906
+ message: "The upstream provider declined this call: the endpoint is outside this deployment's provider plan or quota (runtime_status plan_required). The arguments were not the problem.",
56907
+ suggestion: "Use search_tools to pick another tool for the same data. Retrying unchanged will fail again."
56908
+ },
56909
+ rate_limited: {
56910
+ message: "The upstream provider rate limit was hit (runtime_status rate_limited).",
56911
+ suggestion: "Wait about 60 seconds before one retry, or narrow the request with this tool's filters."
56912
+ },
56913
+ missing_key: {
56914
+ suggestion: "This provider has no working credential on this deployment. Use search_tools to pick another tool for the same data; self-hosted deployments can configure the provider's API key."
56915
+ }
56916
+ };
56917
+ var VENDOR_ACCOUNT_URL = /https?:\/\/[^\s"')\]]*(?:billing|pricing|plan|upgrade|admin|dashboard|account|subscri|checkout)[^\s"')\]]*/gi;
56918
+ function sanitizeProviderError(message, suggestion) {
56919
+ const copy = PROVIDER_SIDE_ERROR_COPY[classifyRuntimeStatusFromError(message)];
56920
+ return {
56921
+ message: (copy?.message ?? message).replace(
56922
+ VENDOR_ACCOUNT_URL,
56923
+ "[link removed]"
56924
+ ),
56925
+ suggestion: copy?.suggestion ?? suggestion,
56926
+ providerSide: Boolean(copy)
56927
+ };
56928
+ }
56929
+ function collapseErrorMarkers(text) {
56930
+ return text.replace(/(?:\*\*Error:\*\*\s*){2,}/g, "**Error:** ");
56931
+ }
56285
56932
  function normalizeErrorText(text, toolName, args) {
56286
56933
  let errorMsg = "";
56287
56934
  let code = "";
56288
56935
  let suggestion = "";
56289
- let alternatives = "";
56936
+ let alternatives = [];
56290
56937
  let documentation = "";
56291
56938
  try {
56292
56939
  const parsed = JSON.parse(text);
56293
56940
  if (parsed && typeof parsed === "object" && typeof parsed.error === "string") {
56294
56941
  errorMsg = parsed.error;
56942
+ if (typeof parsed.message === "string" && parsed.message.trim() && !errorMsg.includes(parsed.message)) {
56943
+ errorMsg += `: ${parsed.message.replace(/https?:\/\/\S+/g, "[url]")}`;
56944
+ }
56295
56945
  code = typeof parsed.code === "string" ? parsed.code : "";
56296
56946
  suggestion = parsed.suggestion || "";
56297
56947
  if (Array.isArray(parsed.alternatives) && parsed.alternatives.length > 0) {
56298
- alternatives = parsed.alternatives.join(", ");
56948
+ alternatives = parsed.alternatives.map(String);
56299
56949
  }
56300
56950
  documentation = parsed.documentation || "";
56301
56951
  }
@@ -56303,7 +56953,18 @@ function normalizeErrorText(text, toolName, args) {
56303
56953
  }
56304
56954
  if (errorMsg) {
56305
56955
  const bareErrorMsg = errorMsg.replace(/^(?:\s*\*\*Error:\*\*\s*)+/, "");
56306
- let normalized = `**Error:** ${bareErrorMsg}`;
56956
+ if (bareErrorMsg.includes("\n\n**Code:**")) {
56957
+ return collapseErrorMarkers(`**Error:** ${bareErrorMsg}`);
56958
+ }
56959
+ const safe = sanitizeProviderError(bareErrorMsg, suggestion);
56960
+ suggestion = safe.suggestion;
56961
+ const prefix = toolName?.split("_")[0];
56962
+ if (safe.providerSide && prefix) {
56963
+ alternatives = alternatives.filter(
56964
+ (name) => !name.startsWith(`${prefix}_`)
56965
+ );
56966
+ }
56967
+ let normalized = `**Error:** ${safe.message}`;
56307
56968
  normalized += `
56308
56969
 
56309
56970
  **Code:** ${code || "API_ERROR"}`;
@@ -56319,15 +56980,29 @@ function normalizeErrorText(text, toolName, args) {
56319
56980
 
56320
56981
  **Params:** ${truncated}`;
56321
56982
  }
56322
- if (alternatives) normalized += `
56983
+ if (alternatives.length > 0) {
56984
+ normalized += `
56323
56985
 
56324
- **Alternatives:** ${alternatives}`;
56986
+ **Alternatives:** ${alternatives.join(", ")}`;
56987
+ }
56325
56988
  if (documentation) normalized += `
56326
56989
 
56327
56990
  **Documentation:** ${documentation}`;
56328
56991
  return normalized;
56329
56992
  }
56330
- let result2 = text;
56993
+ let result2 = collapseErrorMarkers(text);
56994
+ const head = /^\*\*Error:\*\* (.*?)(?=\n\n\*\*|$)/s.exec(result2);
56995
+ if (head) {
56996
+ const safe = sanitizeProviderError(head[1], "");
56997
+ result2 = result2.replace(head[0], `**Error:** ${safe.message}`);
56998
+ if (safe.suggestion) {
56999
+ result2 = result2.replace(
57000
+ /\*\*Suggestion:\*\* [^\n]*/,
57001
+ `**Suggestion:** ${safe.suggestion}`
57002
+ );
57003
+ }
57004
+ }
57005
+ result2 = result2.replace(VENDOR_ACCOUNT_URL, "[link removed]");
56331
57006
  if (toolName && !result2.includes("**Tool:**")) {
56332
57007
  result2 += `
56333
57008
 
@@ -57032,7 +57707,7 @@ function agentErrorCopy(status, context = {}) {
57032
57707
  case "plan_required":
57033
57708
  return {
57034
57709
  cause: `This ${provider} endpoint needs a paid plan, quota, or credits that this deployment does not have.`,
57035
- next_action: "Use search_tools to pick an alternative provider tool for the same data, or upgrade the plan. Retrying unchanged will fail again."
57710
+ next_action: "Use search_tools to pick an alternative provider tool for the same data. Retrying unchanged will fail again."
57036
57711
  };
57037
57712
  case "rate_limited":
57038
57713
  return {
@@ -57606,17 +58281,17 @@ function fitPayloadWithinReceiptBudget(value, hive, isError = false) {
57606
58281
  const payloadBudget = Math.max(256, CHARACTER_LIMIT - receiptLength + 1);
57607
58282
  const truncated = smartTruncate(JSON.stringify(payload), payloadBudget);
57608
58283
  const parsed = parseToolText(truncated);
57609
- let boundedPayload = toHiveResultPayload(parsed);
57610
- hive.result_digest = digestHiveCanonicalJson(boundedPayload);
57611
- if (JSON.stringify({ ...boundedPayload, _hive: hive }).length > CHARACTER_LIMIT) {
57612
- boundedPayload = {
58284
+ let boundedPayload2 = toHiveResultPayload(parsed);
58285
+ hive.result_digest = digestHiveCanonicalJson(boundedPayload2);
58286
+ if (JSON.stringify({ ...boundedPayload2, _hive: hive }).length > CHARACTER_LIMIT) {
58287
+ boundedPayload2 = {
57613
58288
  _truncated: true,
57614
58289
  _original_length: JSON.stringify(payload).length,
57615
58290
  _note: "Response omitted because the provider payload plus its execution receipt exceeded the response limit. Re-call with narrower parameters or filters."
57616
58291
  };
57617
- hive.result_digest = digestHiveCanonicalJson(boundedPayload);
58292
+ hive.result_digest = digestHiveCanonicalJson(boundedPayload2);
57618
58293
  }
57619
- return boundedPayload;
58294
+ return boundedPayload2;
57620
58295
  }
57621
58296
  function createHiveExecutionMetadata(endpoint, durationMs, runtimeStatus, source, warnings = [], exec, integrity = {}, credit = {}) {
57622
58297
  const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -57665,6 +58340,27 @@ function createHiveExecutionMetadata(endpoint, durationMs, runtimeStatus, source
57665
58340
  function unchargedCredit(context) {
57666
58341
  return context.creditCost === void 0 ? {} : { creditCost: context.creditCost, creditsUsed: 0 };
57667
58342
  }
58343
+ var FAILED_CALL_REFUND_WARNING = "This call failed, so the credit it used was refunded.";
58344
+ async function refundFailedCall(billing, toolName, runtimeStatus, warnings) {
58345
+ if (!billing?.refund || billing.credits_used <= 0) return billing;
58346
+ try {
58347
+ const outcome = await billing.refund({ toolName, runtimeStatus });
58348
+ if (!outcome.refunded) return billing;
58349
+ warnings.push(FAILED_CALL_REFUND_WARNING);
58350
+ return {
58351
+ ...billing,
58352
+ credits_used: 0,
58353
+ ...outcome.creditsRemaining !== void 0 && {
58354
+ credits_remaining: outcome.creditsRemaining
58355
+ }
58356
+ };
58357
+ } catch (error) {
58358
+ secureLogger.error("Credit refund for a failed call failed", error, {
58359
+ toolName
58360
+ });
58361
+ return billing;
58362
+ }
58363
+ }
57668
58364
  function chargedCredit(context, billing) {
57669
58365
  if (context.creditCost === void 0 && !billing) return {};
57670
58366
  return {
@@ -58134,6 +58830,17 @@ function executionGateFailureResult(endpoint, gate, durationMs, args, credit = {
58134
58830
  };
58135
58831
  }
58136
58832
  async function executeEndpoint(endpoint, args, context) {
58833
+ const result2 = await executeEndpointUnchecked(endpoint, args, context);
58834
+ if (result2.mcpResult.isError && endpoint.tool.outputSchema && result2.mcpResult.structuredContent !== void 0 && validateJsonSchema(
58835
+ result2.mcpResult.structuredContent,
58836
+ endpoint.tool.outputSchema
58837
+ ).length > 0) {
58838
+ const { structuredContent: _omitted, ...textOnly } = result2.mcpResult;
58839
+ return { ...result2, mcpResult: textOnly };
58840
+ }
58841
+ return result2;
58842
+ }
58843
+ async function executeEndpointUnchecked(endpoint, args, context) {
58137
58844
  const start = context.startMs ?? Date.now();
58138
58845
  const validationErrors = validateEndpointArgs(endpoint, args);
58139
58846
  if (validationErrors.length > 0) {
@@ -58249,18 +58956,33 @@ async function executeEndpoint(endpoint, args, context) {
58249
58956
  const guardLimit = getAnnotatedMaxResultSizeChars(endpoint.tool) ?? DEFAULT_RESULT_SIZE_GUARD_CHARS;
58250
58957
  const successText = getTextContent(normalized);
58251
58958
  if (successText && successText.length > guardLimit) {
58959
+ const guardParams = inputSchemaParamNames(endpoint.tool.inputSchema);
58252
58960
  bounded = {
58253
58961
  ...normalized,
58254
58962
  content: [
58255
- { type: "text", text: smartTruncate(successText, guardLimit) }
58963
+ {
58964
+ type: "text",
58965
+ text: smartTruncate(successText, guardLimit, {
58966
+ params: guardParams,
58967
+ offset: args?.offset
58968
+ })
58969
+ }
58256
58970
  ]
58257
58971
  };
58258
58972
  execForHive = { ...exec, truncated: true };
58259
58973
  guardWarnings.push(
58260
- `Response exceeded ${guardLimit} characters and was truncated for model-context safety. Narrow the request with limit/page parameters or response_format="concise"; tools annotated with anthropic/maxResultSizeChars keep full headroom.`
58974
+ `Response exceeded ${guardLimit} characters and was truncated for model-context safety. ${truncationRecallHint(guardParams)}`
58261
58975
  );
58262
58976
  }
58263
58977
  }
58978
+ if (normalized.isError) {
58979
+ billing = await refundFailedCall(
58980
+ billing,
58981
+ endpoint.tool.name,
58982
+ runtimeStatus,
58983
+ guardWarnings
58984
+ );
58985
+ }
58264
58986
  const durationMs = Date.now() - start;
58265
58987
  const hive = createHiveExecutionMetadata(
58266
58988
  endpoint,
@@ -58340,12 +59062,21 @@ async function executeEndpoint(endpoint, args, context) {
58340
59062
  endpoint.tool.name,
58341
59063
  args
58342
59064
  );
59065
+ const thrownWarnings = [
59066
+ "Tool execution threw before returning a provider result."
59067
+ ];
59068
+ billing = await refundFailedCall(
59069
+ billing,
59070
+ endpoint.tool.name,
59071
+ runtimeStatus,
59072
+ thrownWarnings
59073
+ );
58343
59074
  const hive = createHiveExecutionMetadata(
58344
59075
  endpoint,
58345
59076
  durationMs,
58346
59077
  runtimeStatus,
58347
59078
  "unavailable",
58348
- ["Tool execution threw before returning a provider result."],
59079
+ thrownWarnings,
58349
59080
  void 0,
58350
59081
  {
58351
59082
  input: args ?? {},
@@ -58453,7 +59184,7 @@ var CHAIN_ID_NAMES = {};
58453
59184
  for (const [name, id] of Object.entries(GOPLUS_CHAIN_IDS)) {
58454
59185
  if (!(id in CHAIN_ID_NAMES)) CHAIN_ID_NAMES[id] = name;
58455
59186
  }
58456
- var CHAIN_ALIASES = [
59187
+ var CHAIN_ALIASES2 = [
58457
59188
  "chain",
58458
59189
  "network",
58459
59190
  "chain_id",
@@ -58490,7 +59221,7 @@ var HERO_ARG_ALIASES = {
58490
59221
  "name"
58491
59222
  ],
58492
59223
  address: CONTRACT_ADDRESS_ALIASES,
58493
- chain: CHAIN_ALIASES,
59224
+ chain: CHAIN_ALIASES2,
58494
59225
  vs_currency: [
58495
59226
  "vs_currency",
58496
59227
  "vsCurrency",
@@ -58503,7 +59234,7 @@ var HERO_ARG_ALIASES = {
58503
59234
  },
58504
59235
  check_token_safety: {
58505
59236
  address: [...CONTRACT_ADDRESS_ALIASES, "token"],
58506
- chain: CHAIN_ALIASES,
59237
+ chain: CHAIN_ALIASES2,
58507
59238
  response_format: RESPONSE_FORMAT_ALIASES
58508
59239
  },
58509
59240
  get_wallet_portfolio: {
@@ -58516,7 +59247,8 @@ var HERO_ARG_ALIASES = {
58516
59247
  "account_address",
58517
59248
  "owner"
58518
59249
  ],
58519
- chain: CHAIN_ALIASES,
59250
+ chain: CHAIN_ALIASES2,
59251
+ include_spam: ["include_spam", "includeSpam", "show_spam"],
58520
59252
  response_format: RESPONSE_FORMAT_ALIASES
58521
59253
  }
58522
59254
  };
@@ -58803,9 +59535,10 @@ function heroTools(endpoints, options = {}) {
58803
59535
  address,
58804
59536
  chain: chainLabel,
58805
59537
  risk_flags: flags,
59538
+ ...isRecord3(entry2) ? { summary: summarizeTokenSafety(entry2) } : {},
58806
59539
  ...isRecord3(entry2) && entry2.holder_count !== void 0 ? { holder_count: entry2.holder_count } : {},
58807
59540
  ...isRecord3(entry2) && entry2.token_name !== void 0 ? { token_name: entry2.token_name } : {},
58808
- note: "risk_flags are raw GoPlus datapoints; treat '1' as flagged. Use response_format=detailed for the full report."
59541
+ note: "risk_flags are raw GoPlus datapoints; treat '1' as flagged. summary.verdicts restates the scam signals in plain language; an empty list means none fired, not that the token is safe. Use response_format=detailed for the full report."
58809
59542
  };
58810
59543
  };
58811
59544
  const checkTokenSafety = {
@@ -58817,7 +59550,7 @@ function heroTools(endpoints, options = {}) {
58817
59550
  tool: {
58818
59551
  name: "check_token_safety",
58819
59552
  title: "Check Token Safety",
58820
- description: "Token security screen for a contract address: honeypot, taxes, ownership, and related GoPlus risk datapoints. EVM chains use chain + address; Solana addresses are detected automatically.",
59553
+ description: "Token security screen for a contract address: honeypot, taxes, ownership, and related GoPlus risk datapoints, plus a summary of LP lock status, top-holder concentration, and creator/owner balances with plain-language verdicts. EVM chains use chain + address; Solana addresses are detected automatically.",
58821
59554
  inputSchema: zodToInputSchema(tokenSafetySchema),
58822
59555
  outputSchema: heroOutputSchema("check_token_safety"),
58823
59556
  annotations: VOLATILE_READ_ANNOTATIONS
@@ -58861,9 +59594,13 @@ function heroTools(endpoints, options = {}) {
58861
59594
  chain: z2.string().trim().max(40).optional().describe(
58862
59595
  'Chain name, for example "eth", "base", "arbitrum", or "solana". Defaults to eth for 0x addresses; Solana is auto-detected.'
58863
59596
  ),
59597
+ include_spam: z2.boolean().optional().describe(
59598
+ "Show likely spam airdrops (tokens with a URL or claim lure in the name, and on EVM chains tokens with no market price). Default false; the response says how many were hidden."
59599
+ ),
58864
59600
  response_format: RESPONSE_FORMAT
58865
59601
  });
58866
59602
  const MAX_CONCISE_TOKENS = 15;
59603
+ const SPAM_LURE = /(https?:\/\/|www\.|t\.me\/|\.(com|xyz|io|org|net|app|site|top|lol|info|claim|gift)\b|\bclaim|\bvisit\b|airdrop|\breward)/i;
58867
59604
  const NATIVE_SYMBOLS = {
58868
59605
  eth: "ETH",
58869
59606
  ethereum: "ETH",
@@ -58903,10 +59640,10 @@ function heroTools(endpoints, options = {}) {
58903
59640
  };
58904
59641
  const usdFromPrices = (token, balance) => {
58905
59642
  if (!Array.isArray(token.tokenPrices)) return null;
58906
- const usd = token.tokenPrices.find(
59643
+ const usd2 = token.tokenPrices.find(
58907
59644
  (price) => isRecord3(price) && price.currency === "usd"
58908
59645
  );
58909
- const rate = usd && isRecord3(usd) ? Number(usd.value) : NaN;
59646
+ const rate = usd2 && isRecord3(usd2) ? Number(usd2.value) : NaN;
58910
59647
  const quantity = balance == null ? NaN : Number(balance);
58911
59648
  const value = rate * quantity;
58912
59649
  if (!Number.isFinite(value)) return null;
@@ -58923,7 +59660,7 @@ function heroTools(endpoints, options = {}) {
58923
59660
  }
58924
59661
  return [];
58925
59662
  };
58926
- const trimWalletPortfolio = (address, chainLabel) => (payload) => {
59663
+ const trimWalletPortfolio = (address, chainLabel, includeSpam = false, priced = false) => (payload) => {
58927
59664
  const walletLower = address.trim().toLowerCase();
58928
59665
  const tokens = firstArray(payload, ["tokens", "items", "balances"]);
58929
59666
  const summarized = tokens.map((token) => {
@@ -58946,18 +59683,28 @@ function heroTools(endpoints, options = {}) {
58946
59683
  usd_value: token.totalValueUsd ?? token.valueUsd ?? token.usd_value ?? (typeof token.totalValueUsdCents === "number" ? token.totalValueUsdCents / 100 : usdFromPrices(token, balance))
58947
59684
  };
58948
59685
  });
58949
- const held = summarized.filter(
59686
+ const nonZero = summarized.filter(
58950
59687
  (token) => !isRecord3(token) || token.balance !== "0"
58951
59688
  );
59689
+ const isSpam = (token) => isRecord3(token) && token.address != null && (SPAM_LURE.test(`${token.name ?? ""} ${token.symbol ?? ""}`) || priced && token.usd_value == null);
59690
+ const held = includeSpam ? nonZero : nonZero.filter((t) => !isSpam(t));
59691
+ const hidden = nonZero.length - held.length;
58952
59692
  return {
58953
59693
  address,
58954
59694
  chain: chainLabel,
58955
59695
  token_count: held.length,
59696
+ ...hidden > 0 ? {
59697
+ spam_hidden: {
59698
+ count: hidden,
59699
+ reason: priced ? "no market price, or a URL/claim lure in the name" : "a URL/claim lure in the name",
59700
+ note: "Likely spam airdrops; pass include_spam=true to show them."
59701
+ }
59702
+ } : {},
58956
59703
  tokens: held.slice(0, MAX_CONCISE_TOKENS),
58957
59704
  ...held.length > MAX_CONCISE_TOKENS ? {
58958
59705
  note: `Showing the first ${MAX_CONCISE_TOKENS} of ${held.length} held tokens (zero balances omitted). Use response_format=detailed for the full provider payload.`
58959
- } : summarized.length > held.length ? {
58960
- note: `${summarized.length - held.length} zero-balance tokens omitted. Use response_format=detailed for the full provider payload.`
59706
+ } : summarized.length > nonZero.length ? {
59707
+ note: `${summarized.length - nonZero.length} zero-balance tokens omitted. Use response_format=detailed for the full provider payload.`
58961
59708
  } : {}
58962
59709
  };
58963
59710
  };
@@ -58970,7 +59717,7 @@ function heroTools(endpoints, options = {}) {
58970
59717
  tool: {
58971
59718
  name: "get_wallet_portfolio",
58972
59719
  title: "Get Wallet Portfolio",
58973
- description: "Token holdings for a wallet with balances and USD values where available. EVM chains route through Alchemy; Solana wallets are detected automatically and route through Helius.",
59720
+ description: "Token holdings for a wallet with balances and USD values where available. Likely spam airdrops are hidden and counted (include_spam=true shows them). EVM chains route through Alchemy; Solana wallets are detected automatically and route through Helius.",
58974
59721
  inputSchema: zodToInputSchema(walletPortfolioSchema),
58975
59722
  outputSchema: heroOutputSchema("get_wallet_portfolio"),
58976
59723
  annotations: VOLATILE_READ_ANNOTATIONS
@@ -58986,14 +59733,18 @@ function heroTools(endpoints, options = {}) {
58986
59733
  `Invalid arguments: ${formatSchemaIssues(parsed.error)}`
58987
59734
  );
58988
59735
  }
58989
- const { address, chain } = parsed.data;
59736
+ const { address, chain, include_spam: includeSpam } = parsed.data;
58990
59737
  const format = heroFormat(parsed.data.response_format);
58991
59738
  if (looksLikeNameService(address)) return nameServiceError(address);
58992
59739
  if (isSolanaChain(chain, address)) {
58993
59740
  const result3 = await executeUnderlying("helius_get_wallet_balances", {
58994
59741
  wallet: address
58995
59742
  }, "get_wallet_portfolio");
58996
- return respond(result3, format, trimWalletPortfolio(address, "solana"));
59743
+ return respond(
59744
+ result3,
59745
+ format,
59746
+ trimWalletPortfolio(address, "solana", includeSpam)
59747
+ );
58997
59748
  }
58998
59749
  const normalized = normalizeChain(chain);
58999
59750
  const network = ALCHEMY_NETWORKS[normalized];
@@ -59008,11 +59759,107 @@ function heroTools(endpoints, options = {}) {
59008
59759
  withMetadata: true,
59009
59760
  withPrices: true
59010
59761
  }, "get_wallet_portfolio");
59011
- return respond(result2, format, trimWalletPortfolio(address, normalized));
59762
+ return respond(
59763
+ result2,
59764
+ format,
59765
+ trimWalletPortfolio(address, normalized, includeSpam, true)
59766
+ );
59012
59767
  }
59013
59768
  };
59014
59769
  return [getTokenPrice, checkTokenSafety, getWalletPortfolio];
59015
59770
  }
59771
+ var FLAG_VERDICTS = {
59772
+ is_honeypot: "Honeypot: GoPlus says this token cannot be sold",
59773
+ cannot_sell_all: "Holders cannot sell their whole balance",
59774
+ hidden_owner: "The contract has a hidden owner",
59775
+ can_take_back_ownership: "Ownership can be taken back after renouncing",
59776
+ is_mintable: "The owner can mint more supply",
59777
+ transfer_pausable: "Transfers can be paused",
59778
+ is_blacklisted: "The contract can blacklist addresses",
59779
+ owner_change_balance: "The owner can change balances",
59780
+ selfdestruct: "The contract can self-destruct",
59781
+ is_airdrop_scam: "GoPlus flags this as an airdrop scam"
59782
+ };
59783
+ function percentOf(value, fraction) {
59784
+ const n = typeof value === "string" || typeof value === "number" ? Number(value) : NaN;
59785
+ if (!Number.isFinite(n)) return null;
59786
+ return Math.round((fraction ? n * 100 : n) * 1e3) / 1e3;
59787
+ }
59788
+ function holderView(holder) {
59789
+ return {
59790
+ address: holder.address ?? holder.account ?? null,
59791
+ // Solana rows use account
59792
+ percent: percentOf(holder.percent, true),
59793
+ is_contract: holder.is_contract === 1 || holder.is_contract === "1",
59794
+ is_locked: holder.is_locked === 1 || holder.is_locked === "1",
59795
+ tag: holder.tag || null
59796
+ };
59797
+ }
59798
+ function summarizeTokenSafety(entry2) {
59799
+ const verdicts = [];
59800
+ for (const [key, sentence] of Object.entries(FLAG_VERDICTS)) {
59801
+ if (entry2[key] === "1") verdicts.push(sentence);
59802
+ }
59803
+ for (const side of ["buy", "sell"]) {
59804
+ const tax = percentOf(entry2[`${side}_tax`], true);
59805
+ if (tax !== null && tax >= 10) verdicts.push(`${side === "buy" ? "Buy" : "Sell"} tax is ${tax}%`);
59806
+ }
59807
+ const lpRows = Array.isArray(entry2.lp_holders) ? entry2.lp_holders.filter(isRecord3) : [];
59808
+ let lp = null;
59809
+ if (lpRows.length > 0) {
59810
+ const views = lpRows.map(holderView);
59811
+ const top = views.reduce((a, b) => (b.percent ?? 0) > (a.percent ?? 0) ? b : a);
59812
+ const locked = views.reduce((sum, v) => sum + (v.is_locked ? v.percent ?? 0 : 0), 0);
59813
+ lp = {
59814
+ holder_count: entry2.lp_holder_count ?? lpRows.length,
59815
+ locked_percent: Math.round(locked * 1e3) / 1e3,
59816
+ top_holder: top
59817
+ };
59818
+ if (locked < 50 && !top.is_locked && (top.percent ?? 0) >= 50) {
59819
+ verdicts.push(
59820
+ `LP not locked: ${top.percent}% held by one ${top.is_contract ? "contract" : "EOA"}`
59821
+ );
59822
+ } else if (locked < 50) {
59823
+ verdicts.push(`LP mostly unlocked: only ${Math.round(locked * 1e3) / 1e3}% of LP is locked`);
59824
+ }
59825
+ }
59826
+ const holderRows = Array.isArray(entry2.holders) ? entry2.holders.filter(isRecord3) : [];
59827
+ let topHolders = null;
59828
+ if (holderRows.length > 0) {
59829
+ const views = holderRows.map(holderView);
59830
+ const pairs = new Set(
59831
+ (Array.isArray(entry2.dex) ? entry2.dex.filter(isRecord3) : []).map((d) => String(d.pair ?? "").toLowerCase())
59832
+ );
59833
+ const owners = views.filter((v) => !pairs.has(String(v.address).toLowerCase()));
59834
+ const top10 = owners.slice(0, 10).reduce((sum, v) => sum + (v.percent ?? 0), 0);
59835
+ const largest = owners[0] ?? null;
59836
+ topHolders = {
59837
+ largest,
59838
+ top10_percent: Math.round(top10 * 1e3) / 1e3,
59839
+ excludes_dex_pairs: pairs.size > 0
59840
+ };
59841
+ if (largest && (largest.percent ?? 0) >= 20) {
59842
+ verdicts.push(
59843
+ `One address holds ${largest.percent}% of supply (${largest.is_contract ? "contract" : "EOA"}, ${largest.is_locked ? "locked" : "not locked"})`
59844
+ );
59845
+ }
59846
+ if (top10 >= 50) verdicts.push(`Top 10 holders hold ${Math.round(top10 * 10) / 10}% of supply`);
59847
+ }
59848
+ const creator = entry2.creator_address ? {
59849
+ address: entry2.creator_address,
59850
+ balance: entry2.creator_balance ?? null,
59851
+ percent: percentOf(entry2.creator_percent, true)
59852
+ } : null;
59853
+ const owner = entry2.owner_address ? {
59854
+ address: entry2.owner_address,
59855
+ balance: entry2.owner_balance ?? null,
59856
+ percent: percentOf(entry2.owner_percent, true)
59857
+ } : null;
59858
+ for (const [label, who] of [["Creator", creator], ["Owner", owner]]) {
59859
+ if (who && (who.percent ?? 0) >= 5) verdicts.push(`${label} still holds ${who.percent}% of supply`);
59860
+ }
59861
+ return { verdicts, lp, top_holders: topHolders, creator, owner };
59862
+ }
59016
59863
  function heroOutputSchema(name) {
59017
59864
  return HERO_TOOL_OUTPUT_SCHEMAS[name];
59018
59865
  }
@@ -59154,7 +60001,7 @@ var TASK_TOOLSET_ROUTES = {
59154
60001
  "holders",
59155
60002
  "Measure holder concentration with a bounded holder read.",
59156
60003
  "get_token_top_holders",
59157
- "moralis_get_token_holder_metrics",
60004
+ "get_token_holder_distribution",
59158
60005
  PROVIDER_FALLBACK
59159
60006
  ),
59160
60007
  step(
@@ -59314,15 +60161,14 @@ var TASK_TOOLSET_ROUTES = {
59314
60161
  "simulation",
59315
60162
  "Simulate execution without broadcasting.",
59316
60163
  "tenderly_simulate_transaction",
59317
- "alchemy_simulate_execution",
60164
+ "tenderly_node_simulate_transaction",
59318
60165
  PROVIDER_FALLBACK
59319
60166
  ),
59320
60167
  step(
59321
60168
  "risk",
59322
60169
  "Inspect simulated asset changes.",
59323
- "alchemy_simulate_asset_changes",
59324
60170
  "simulate_evm_transaction",
59325
- PROVIDER_FALLBACK
60171
+ null
59326
60172
  )
59327
60173
  ],
59328
60174
  stopCondition: "Stop after decoding, execution outcome, and asset effects are clear; never broadcast or mutate chain state."
@@ -60146,6 +60992,7 @@ var TASK_TOOLSET_CATALOG = [
60146
60992
  optionalIdentifiers: ["symbol", "pair address", "time range"],
60147
60993
  recommendedTools: [
60148
60994
  "get_token_top_holders",
60995
+ "get_token_holder_distribution",
60149
60996
  "get_token_pools",
60150
60997
  "get_token_ohlcv",
60151
60998
  "get_token_info",
@@ -60314,8 +61161,6 @@ var TASK_TOOLSET_CATALOG = [
60314
61161
  "alchemy_solana_get_compression_indexer_slot",
60315
61162
  "alchemy_solana_get_latest_compression_signatures",
60316
61163
  "alchemy_solana_get_latest_non_voting_signatures",
60317
- "alchemy_simulate_asset_changes",
60318
- "alchemy_simulate_execution",
60319
61164
  "simulate_evm_transaction",
60320
61165
  "defillama_get_hacks"
60321
61166
  ],
@@ -61545,7 +62390,11 @@ function getTaskOutputSchema(taskToolsetId) {
61545
62390
  }
61546
62391
 
61547
62392
  // src/services/execution-fallback.ts
61548
- var FALLBACK_TRIGGER_STATUSES = /* @__PURE__ */ new Set(["failing", "missing_key"]);
62393
+ var FALLBACK_TRIGGER_STATUSES = /* @__PURE__ */ new Set([
62394
+ "failing",
62395
+ "missing_key",
62396
+ "plan_required"
62397
+ ]);
61549
62398
  function findRouteFallback(primaryToolName) {
61550
62399
  for (const toolset of TASK_TOOLSETS) {
61551
62400
  if (toolset.fallbackTools.length === 0) continue;
@@ -61588,9 +62437,7 @@ async function executeEndpointWithRouteFallback(endpoints, endpoint, args, conte
61588
62437
  const fallbackProviderStatus = getProviderRuntimeStatus(
61589
62438
  getProviderDescriptorForEndpoint(fallback)
61590
62439
  );
61591
- if (fallbackProviderStatus === "missing_key" || fallbackProviderStatus === "failing") {
61592
- return primary;
61593
- }
62440
+ if (FALLBACK_TRIGGER_STATUSES.has(fallbackProviderStatus)) return primary;
61594
62441
  if (!argsMapCleanly(fallback, args)) return primary;
61595
62442
  const fallbackResult = await executeEndpoint(fallback, args, {
61596
62443
  ...context,
@@ -64376,7 +65223,7 @@ async function runOAuthLifecycleHealth(overrides = {}) {
64376
65223
  }
64377
65224
 
64378
65225
  // src/health/selfTest.ts
64379
- import Coingecko3 from "@coingecko/coingecko-typescript";
65226
+ import Coingecko4 from "@coingecko/coingecko-typescript";
64380
65227
 
64381
65228
  // src/tools/goplus/goplusClient.ts
64382
65229
  import { createHash as createHash8 } from "crypto";
@@ -65721,6 +66568,87 @@ var CCXTClient = class {
65721
66568
  }
65722
66569
  };
65723
66570
 
66571
+ // src/health/answerProbes.ts
66572
+ var ETH_WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
66573
+ var ETH_PEPE = "0x6982508145454Ce325dDbE47a25d4ec3d2311933";
66574
+ function isRecord4(value) {
66575
+ return !!value && typeof value === "object" && !Array.isArray(value);
66576
+ }
66577
+ function largestArray(value, depth = 0) {
66578
+ if (Array.isArray(value)) return value;
66579
+ if (!isRecord4(value) || depth > 4) return [];
66580
+ let best = [];
66581
+ for (const child of Object.values(value)) {
66582
+ const candidate = largestArray(child, depth + 1);
66583
+ if (candidate.length > best.length) best = candidate;
66584
+ }
66585
+ return best;
66586
+ }
66587
+ function rowsOf(payload) {
66588
+ return isRecord4(payload) && Array.isArray(payload.rows) ? payload.rows : largestArray(payload);
66589
+ }
66590
+ function returnedCountMismatch(payload, rows) {
66591
+ const returned = isRecord4(payload) ? payload.returnedCount : void 0;
66592
+ return typeof returned === "number" && returned !== rows.length ? `returnedCount ${returned} != rows.length ${rows.length}` : null;
66593
+ }
66594
+ var ANSWER_PROBES = {
66595
+ polymarket_search_markets: {
66596
+ // The "JD Vance wins 2028" market resolves 2028-11-07; the event-level
66597
+ // fixture in selfTest's TOOL_PROBE_ARGS points at the same market.
66598
+ args: { query: "vance 2028", limit: 3 },
66599
+ check: (payload) => {
66600
+ const rows = rowsOf(payload);
66601
+ if (rows.length === 0)
66602
+ return "text search for 'vance 2028' found no markets";
66603
+ const unmatched = rows.filter((row) => {
66604
+ const text = JSON.stringify(row).toLowerCase();
66605
+ return !text.includes("vance") || !text.includes("2028");
66606
+ });
66607
+ if (unmatched.length > 0) {
66608
+ return `${unmatched.length} of ${rows.length} rows do not match every query term`;
66609
+ }
66610
+ return returnedCountMismatch(payload, rows);
66611
+ }
66612
+ },
66613
+ get_token_top_holders: {
66614
+ args: { network: "eth", address: ETH_WETH, limit: 20 },
66615
+ check: (payload) => {
66616
+ const holders = largestArray(payload);
66617
+ return holders.length > 10 ? null : `asked for 20 WETH holders, got ${holders.length}`;
66618
+ }
66619
+ },
66620
+ rwa_get_hyperliquid_dex_perps: {
66621
+ args: { limit: 100 },
66622
+ check: (payload) => {
66623
+ const rows = rowsOf(payload);
66624
+ if (rows.length === 0) return "no HIP-3 builder-dex rows";
66625
+ return returnedCountMismatch(payload, rows);
66626
+ }
66627
+ },
66628
+ // check_token_safety builds its LP verdict on this GoPlus read.
66629
+ get_token_security: {
66630
+ args: { chainId: "1", contract_addresses: ETH_PEPE },
66631
+ check: (payload) => {
66632
+ const result2 = isRecord4(payload) ? payload.result : void 0;
66633
+ const token = isRecord4(result2) ? Object.values(result2).find(isRecord4) : void 0;
66634
+ if (!token) return "no token entry in the GoPlus result";
66635
+ const lpHolders = token.lp_holders;
66636
+ return Array.isArray(lpHolders) && lpHolders.length > 0 ? null : "PEPE came back without lp_holders";
66637
+ }
66638
+ }
66639
+ };
66640
+ function checkAnswer(probe, result2) {
66641
+ const text = isRecord4(result2) ? result2.content?.[0]?.text : void 0;
66642
+ if (typeof text !== "string") return "response has no text content";
66643
+ let payload;
66644
+ try {
66645
+ payload = JSON.parse(text);
66646
+ } catch {
66647
+ return "response is not JSON";
66648
+ }
66649
+ return probe.check(payload);
66650
+ }
66651
+
65724
66652
  // src/health/selfTest.ts
65725
66653
  function positiveTimeout(name, fallbackMs) {
65726
66654
  const configured = Number(process.env[name]);
@@ -65734,7 +66662,7 @@ var QUICK_PROBE_TIMEOUT = Math.min(
65734
66662
  var ETH_WALLET = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
65735
66663
  var MORALIS_EVM_WALLET = "0xF977814e90dA44bFA03b6295A0616a897441aceC";
65736
66664
  var ETH_USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
65737
- var ETH_WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
66665
+ var ETH_WETH2 = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
65738
66666
  var ETH_USDT = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
65739
66667
  var ETH_BAYC = "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D";
65740
66668
  var ETH_USDC_WETH_PAIR = "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc";
@@ -65791,6 +66719,7 @@ var PROVIDER_RESOURCE_PATTERNS = [
65791
66719
  ["polymarket", /^polymarket(?:[.-]|$)/],
65792
66720
  ["alternative", /^signals-fear-greed$/],
65793
66721
  ["blockchain", /^signals-onchain/],
66722
+ ["blockscout", /^blockscout(?:[.-]|$)/],
65794
66723
  ["fetch", /^fetch(?:[.-]|$)/],
65795
66724
  ["helius", /^helius\./],
65796
66725
  ["moralis", /^moralis\./],
@@ -65861,6 +66790,11 @@ var QUICK_PROBE_ARGS = {
65861
66790
  toolName: "signals_get_onchain_metrics",
65862
66791
  args: { metrics: ["hash_rate"], days: 1 }
65863
66792
  },
66793
+ // One token read plus one holder page on Ethereum (keyless public host).
66794
+ blockscout: {
66795
+ toolName: "get_token_holder_distribution",
66796
+ args: { network: "eth", address: ETH_USDC, max_pages: 1 }
66797
+ },
65864
66798
  fetch: {
65865
66799
  toolName: "fetch_public_api",
65866
66800
  args: { host: "api.coingecko.com", path: "/api/v3/ping" }
@@ -65899,7 +66833,7 @@ var FULL_MODE_DEFAULT_ARGS = {
65899
66833
  vs_currency: "usd",
65900
66834
  vs_currencies: "usd",
65901
66835
  network: "eth",
65902
- address: ETH_WETH,
66836
+ address: ETH_WETH2,
65903
66837
  days: "7"
65904
66838
  },
65905
66839
  defillama: {
@@ -65981,7 +66915,11 @@ var TOOL_PROBE_ARGS = {
65981
66915
  get_rwa_market_chart: { id: "gold", days: "7" },
65982
66916
  get_rwa_issuer: { id: "ondo-tokenized-assets" },
65983
66917
  // Polymarket fixtures verified live 2026-09-16; the event ends 2028-11-07.
65984
- polymarket_search_markets: { query: "election", limit: 3 },
66918
+ get_token_holder_distribution: {
66919
+ network: "eth",
66920
+ address: ETH_USDC,
66921
+ max_pages: 1
66922
+ },
65985
66923
  polymarket_get_events: { slug: "presidential-election-winner-2028" },
65986
66924
  polymarket_get_market: {
65987
66925
  slug: "will-jd-vance-win-the-2028-us-presidential-election"
@@ -67190,7 +68128,7 @@ function getClientForProvider(provider, clients) {
67190
68128
  }
67191
68129
  }
67192
68130
  function buildClients() {
67193
- const coinGeckoClient = new Coingecko3(getCoinGeckoClientOptions());
68131
+ const coinGeckoClient = new Coingecko4(getCoinGeckoClientOptions());
67194
68132
  return {
67195
68133
  coinGeckoClient,
67196
68134
  goplusClient: new GoPlusClient(
@@ -67436,7 +68374,7 @@ async function getEffectiveProbeArgs(toolName, provider, clients, args) {
67436
68374
  return { [idFixture.field]: id };
67437
68375
  });
67438
68376
  }
67439
- const toolFixture = TOOL_PROBE_ARGS[toolName];
68377
+ const toolFixture = ANSWER_PROBES[toolName]?.args ?? TOOL_PROBE_ARGS[toolName];
67440
68378
  if (toolFixture) return toolFixture;
67441
68379
  return projectArgsToToolSchema(
67442
68380
  FULL_MODE_DEFAULT_ARGS[provider] || {},
@@ -67528,6 +68466,16 @@ async function runEndpointProbe(endpoint, provider, clients, args, timeoutMs = P
67528
68466
  );
67529
68467
  }
67530
68468
  }
68469
+ const answerProbe = args ? void 0 : ANSWER_PROBES[toolName];
68470
+ const answerProblem = answerProbe ? checkAnswer(answerProbe, result2) : null;
68471
+ if (answerProblem) {
68472
+ return {
68473
+ name: toolName,
68474
+ status: "failed",
68475
+ duration,
68476
+ error: `Answer check failed: ${answerProblem}`
68477
+ };
68478
+ }
67531
68479
  return { name: toolName, status: "passed", duration };
67532
68480
  } catch (err) {
67533
68481
  const errorMsg = err instanceof Error ? err.message : "Unknown error";
@@ -69582,6 +70530,11 @@ var TOOLSET_INTENT_HINTS = {
69582
70530
  ],
69583
70531
  security_risk: [
69584
70532
  "rugpull",
70533
+ "rug pull",
70534
+ "rug check",
70535
+ "rugcheck",
70536
+ "honeypot",
70537
+ "scam",
69585
70538
  "safe to buy",
69586
70539
  "phishing",
69587
70540
  "malicious",
@@ -69941,6 +70894,9 @@ function catalogEntry(endpoint) {
69941
70894
  material: getMcpCreditCost(endpoint.tool.name) === 1,
69942
70895
  credit_cost: getMcpCreditCost(endpoint.tool.name),
69943
70896
  summary: summarizeToolDescription(endpoint.tool.description ?? ""),
70897
+ // Ranking text: the summary minus its negative guidance, so "Not
70898
+ // recommended for: ... holder info" never counts as a holder match.
70899
+ rankText: summarizeToolDescription(endpoint.tool.description ?? "").split(/not recommended for:|don't use when:|common mistakes:/i)[0].toLowerCase(),
69944
70900
  searchText: [
69945
70901
  endpoint.tool.name,
69946
70902
  endpoint.tool.title ?? getToolTitle(endpoint.tool.name),
@@ -69959,7 +70915,11 @@ function catalogEntry(endpoint) {
69959
70915
  };
69960
70916
  }
69961
70917
  function publicCatalogEntry(entry2) {
69962
- const { searchText: _searchText, ...publicEntry } = entry2;
70918
+ const {
70919
+ searchText: _searchText,
70920
+ rankText: _rankText,
70921
+ ...publicEntry
70922
+ } = entry2;
69963
70923
  return publicEntry;
69964
70924
  }
69965
70925
  function normalizeSearchToken(token) {
@@ -69999,31 +70959,36 @@ function matchesSearchTokens(haystack, query) {
69999
70959
  return tokens.some((token) => textMatchesSearchToken(haystack, token));
70000
70960
  }
70001
70961
  function queryMatchScore(entry2, query) {
70002
- if (!query) return 0;
70962
+ if (!query) return { score: 0, direct: true };
70003
70963
  if (!matchesSearchTokens(entry2.searchText, query)) return null;
70004
70964
  const tokens = parseSearchTokens(query);
70005
- if (tokens.length === 0) return 0;
70965
+ if (tokens.length === 0) return { score: 0, direct: true };
70006
70966
  const nameTitleText = [entry2.name, entry2.title].join(" ").toLowerCase();
70967
+ const summaryText = entry2.rankText;
70007
70968
  const coreText = [
70008
70969
  entry2.name,
70009
70970
  entry2.title,
70010
70971
  entry2.category ?? "",
70011
70972
  entry2.provider,
70012
- entry2.summary
70973
+ summaryText
70013
70974
  ].join(" ").toLowerCase();
70014
70975
  const nameTitleTokenSet = new Set(parseSearchTokens(nameTitleText));
70015
70976
  const coreTextTokenSet = new Set(parseSearchTokens(coreText));
70977
+ const providerTokenSet = new Set(parseSearchTokens(entry2.provider));
70978
+ let direct = false;
70016
70979
  const score = tokens.reduce((total, token) => {
70017
70980
  const exactToken = singularizeToken(normalizeSearchToken(token));
70018
- if (nameTitleTokenSet.has(exactToken)) return total + 24;
70019
- if (coreTextTokenSet.has(exactToken)) return total + 14;
70020
- if (textMatchesSearchToken(nameTitleText, token)) return total + 12;
70021
- if (textMatchesSearchToken(entry2.summary, token)) return total + 6;
70022
- if (textMatchesSearchToken(coreText, token)) return total + 4;
70023
- if (textMatchesSearchToken(entry2.searchText, token)) return total + 2;
70981
+ const providerBonus = providerTokenSet.has(exactToken) ? 8 : 0;
70982
+ const directScore = nameTitleTokenSet.has(exactToken) ? 24 : coreTextTokenSet.has(exactToken) ? 14 : nameTitleText.includes(exactToken) ? 12 : summaryText.includes(exactToken) ? 6 : coreText.includes(exactToken) ? 4 : 0;
70983
+ if (directScore > 0) {
70984
+ direct = true;
70985
+ return total + directScore + providerBonus;
70986
+ }
70987
+ if (textMatchesSearchToken(coreText, token)) return total + 2;
70988
+ if (textMatchesSearchToken(entry2.searchText, token)) return total + 1;
70024
70989
  return total;
70025
70990
  }, 0);
70026
- return score > 0 ? score : null;
70991
+ return score > 0 ? { score, direct } : null;
70027
70992
  }
70028
70993
  function buildProviderByToolName(endpoints) {
70029
70994
  const providerByToolName = /* @__PURE__ */ new Map();
@@ -70102,6 +71067,26 @@ function selectToolsetRoutes(routes, query, routeId) {
70102
71067
  const positive = ranked.filter((item) => item.score > 0);
70103
71068
  return (positive.length > 0 ? positive : ranked).slice(0, 1).map((item) => item.route);
70104
71069
  }
71070
+ function promoteHealthyRouteFallbacks(route, availabilityForTool) {
71071
+ let promoted = false;
71072
+ const steps = route.steps.map((step2) => {
71073
+ const primaryAvailability = availabilityForTool(step2.primaryTool);
71074
+ const fallbackTool = step2.fallback.tool;
71075
+ if (!isUnavailable(primaryAvailability) || !fallbackTool || isUnavailable(availabilityForTool(fallbackTool))) {
71076
+ return step2;
71077
+ }
71078
+ promoted = true;
71079
+ return {
71080
+ ...step2,
71081
+ primaryTool: fallbackTool,
71082
+ fallback: {
71083
+ tool: step2.primaryTool,
71084
+ when: `Promoted fallback: ${step2.primaryTool} is ${primaryAvailability} right now and returns runtime_status, not data. Original fallback condition: ${step2.fallback.when}`
71085
+ }
71086
+ };
71087
+ });
71088
+ return promoted ? { ...route, steps } : route;
71089
+ }
70105
71090
  function publicRouteEntry(route) {
70106
71091
  return {
70107
71092
  id: route.id,
@@ -70115,9 +71100,11 @@ function publicRouteEntry(route) {
70115
71100
  stopCondition: route.stopCondition
70116
71101
  };
70117
71102
  }
70118
- function publicToolsetEntry(entry2, detail, query, routeId) {
71103
+ function publicToolsetEntry(entry2, detail, query, routeId, availabilityForTool = () => "ok") {
70119
71104
  const routes = selectToolsetRoutes(entry2.routes, query, routeId).map(
70120
- publicRouteEntry
71105
+ (route) => publicRouteEntry(
71106
+ promoteHealthyRouteFallbacks(route, availabilityForTool)
71107
+ )
70121
71108
  );
70122
71109
  const compact = {
70123
71110
  id: entry2.id,
@@ -70148,6 +71135,11 @@ function publicToolsetEntry(entry2, detail, query, routeId) {
70148
71135
  }
70149
71136
  function toolsetQueryMatchScore(entry2, query) {
70150
71137
  if (!query) return 0;
71138
+ if (entry2.id === "solana_analysis" && /\b0x[0-9a-f]{40}\b|\b(ethereum|base|arbitrum|optimism|polygon|bsc|avalanche)\b/i.test(
71139
+ query
71140
+ ) && !/\b(solana|sol|spl|pump\.?fun|raydium|jupiter)\b/i.test(query)) {
71141
+ return null;
71142
+ }
70151
71143
  const haystack = [
70152
71144
  entry2.id,
70153
71145
  entry2.label,
@@ -70323,17 +71315,15 @@ function dynamicTools(endpoints, options = {}) {
70323
71315
  const filteredTools = catalogEntries.map((entry2, index) => ({
70324
71316
  entry: entry2,
70325
71317
  index,
70326
- score: routeId ? 100 : queryMatchScore(entry2, query)
70327
- })).filter((item) => item.score !== null).filter(
71318
+ match: routeId ? { score: 100, direct: true } : queryMatchScore(entry2, query)
71319
+ })).filter((item) => item.match !== null).filter(
70328
71320
  (item) => selectedRouteToolNames ? selectedRouteToolNames.has(item.entry.name) : true
70329
71321
  ).filter(
70330
71322
  (entry2) => provider ? entry2.entry.provider.toLowerCase() === provider : true
70331
71323
  ).filter(
70332
71324
  (entry2) => category ? entry2.entry.category?.toLowerCase() === category : true
70333
71325
  ).sort(
70334
- (a, b) => query ? b.score - a.score || a.index - b.index : a.index - b.index
70335
- ).sort(
70336
- (a, b) => Number(isUnavailable(availabilityFor(a.entry.provider))) - Number(isUnavailable(availabilityFor(b.entry.provider)))
71326
+ (a, b) => Number(b.match.direct) - Number(a.match.direct) || Number(isUnavailable(availabilityFor(a.entry.provider))) - Number(isUnavailable(availabilityFor(b.entry.provider))) || (query ? b.match.score - a.match.score : 0) || a.index - b.index
70337
71327
  ).map((item) => item.entry);
70338
71328
  const filteredToolsets = taskToolsetCatalogEntries.map((entry2, index) => ({
70339
71329
  entry: entry2,
@@ -70387,7 +71377,8 @@ function dynamicTools(endpoints, options = {}) {
70387
71377
  entry2,
70388
71378
  parsed.detail ?? "compact",
70389
71379
  query,
70390
- routeId
71380
+ routeId,
71381
+ (toolName) => availabilityFor(providerByToolName.get(toolName) ?? "Hive")
70391
71382
  );
70392
71383
  const providerAvailability = Object.fromEntries(
70393
71384
  entry2.providers.map((name) => [
@@ -71261,7 +72252,7 @@ function getSharedClients() {
71261
72252
  );
71262
72253
  }
71263
72254
  sharedClients = {
71264
- coinGeckoClient: new Coingecko4(getCoinGeckoClientOptions()),
72255
+ coinGeckoClient: new Coingecko5(getCoinGeckoClientOptions()),
71265
72256
  goplusClient,
71266
72257
  ccxtClient: new CCXTClient()
71267
72258
  };
@@ -71294,6 +72285,7 @@ function describeToolReference(toolName, endpointIndex) {
71294
72285
  };
71295
72286
  }
71296
72287
  function describeTaskToolset(toolset, endpointIndex) {
72288
+ const availability = getProviderAvailabilitySnapshot().byProviderName;
71297
72289
  return {
71298
72290
  id: toolset.id,
71299
72291
  kind: toolset.kind,
@@ -71302,7 +72294,12 @@ function describeTaskToolset(toolset, endpointIndex) {
71302
72294
  outcome: toolset.outcome,
71303
72295
  requiredIdentifiers: toolset.requiredIdentifiers,
71304
72296
  optionalIdentifiers: toolset.optionalIdentifiers,
71305
- routes: toolset.routes,
72297
+ routes: toolset.routes.map(
72298
+ (route) => promoteHealthyRouteFallbacks(route, (toolName) => {
72299
+ const endpoint = endpointIndex.get(toolName);
72300
+ return endpoint ? availability[getProviderNameForTool(endpoint)] ?? "ok" : "ok";
72301
+ })
72302
+ ),
71306
72303
  coverageCatalog: {
71307
72304
  recommendedTools: toolset.recommendedTools.map(
71308
72305
  (toolName) => describeToolReference(toolName, endpointIndex)