hive-intelligence 1.9.0 → 1.11.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
 
@@ -7762,9 +7836,8 @@ Don't use when:
7762
7836
  - You need token metadata (use coingecko_get_onchain_token_info)
7763
7837
 
7764
7838
  Error Handling:
7765
- - Returns 404 if network or token address not found
7839
+ - Returns 404 for tokens CoinGecko does not track traders for: wrapped native tokens (WETH, wSOL), stablecoins, and tokens created before 2023-09-01. Works on eth, base, solana and other networks for newer tokens.
7766
7840
  - Requires CoinGecko's Analyst plan or higher (a provider tier, not a Hive plan)
7767
- - Not all networks support trader tracking
7768
7841
  - Rate limited based on API plan`,
7769
7842
  inputSchema: {
7770
7843
  type: "object",
@@ -7803,7 +7876,7 @@ var handler79 = async (client2, args) => {
7803
7876
  if (error?.status === 404) {
7804
7877
  return asTextContentResult(
7805
7878
  {
7806
- error: `Invalid arguments: trader tracking is not available for ${address} on network "${network}" on the CoinGecko tier this account is on (404). Use coingecko_get_onchain_token_top_holders or get_token_top_holders for this token; traders are served on networks such as base.`,
7879
+ error: `Invalid arguments: CoinGecko does not track top traders for ${address} on "${network}" (404). It covers tokens created after 2023-09-01 and excludes wrapped native tokens and stablecoins. Use coingecko_get_onchain_token_top_holders or get_token_top_holders for this token.`,
7807
7880
  code: "VALIDATION_ERROR",
7808
7881
  suggestion: "Call coingecko_get_onchain_token_top_holders with the same network and address."
7809
7882
  },
@@ -8394,17 +8467,18 @@ var handler87 = async (client2, args) => asTextContentResult(
8394
8467
  var get_total_supply_chart_range_default = { metadata: metadata87, tool: tool87, handler: handler87 };
8395
8468
 
8396
8469
  // src/tools/insights/get-insights.ts
8470
+ var INSIGHTS_SOURCE_NOTE = "AI-generated summary written by CoinGecko; not verified by Hive.";
8397
8471
  var metadata88 = {
8398
8472
  resource: "insights",
8399
8473
  operation: "read",
8400
- tags: ["coingecko", "coins", "insights", "research", "enterprise"],
8474
+ tags: ["coingecko", "coins", "insights", "research", "ai-generated"],
8401
8475
  httpMethod: "get",
8402
8476
  httpPath: "/insights",
8403
8477
  operationId: "insights"
8404
8478
  };
8405
8479
  var tool88 = {
8406
8480
  name: "coingecko_get_coin_insights",
8407
- description: `Get CoinGecko's latest coin research insights. Requires a CoinGecko Enterprise API plan and returns a bounded page of at most 20 items.
8481
+ description: `Get CoinGecko's AI-generated coin insights: short summaries of market catalysts, sentiment, and fundamentals. CoinGecko's AI writes these; Hive does not verify them, so treat them as context and confirm facts with market data. Returns a bounded page of at most 20 items.
8408
8482
 
8409
8483
  Args:
8410
8484
  - page (integer, optional): Page number from 1 to 20. Default: 1.
@@ -8414,7 +8488,7 @@ Args:
8414
8488
  - to (string, optional): End date in YYYY-MM-DD format.
8415
8489
 
8416
8490
  Returns:
8417
- JSON array of recent CoinGecko insight records, bounded to the requested page and page size.
8491
+ { source_note, insights: [{ title, description, related_coin_ids, posted_at }] }, bounded to the requested page and page size.
8418
8492
 
8419
8493
  Examples:
8420
8494
  - Latest insights: {"page":1,"per_page":10}
@@ -8429,7 +8503,6 @@ Don't use when:
8429
8503
  - You need live prices or market rankings (use coingecko_get_coins_markets)
8430
8504
 
8431
8505
  Plan and freshness:
8432
- - CoinGecko marks this endpoint Enterprise-only.
8433
8506
  - CoinGecko documents a 60-second cache/update interval.
8434
8507
  - Insight publication time is supplied by CoinGecko; inspect each returned record before treating it as current.`,
8435
8508
  inputSchema: {
@@ -8482,10 +8555,398 @@ var handler88 = async (client2, args) => {
8482
8555
  from: args?.from,
8483
8556
  to: args?.to
8484
8557
  };
8485
- return asTextContentResult(await client2.get("/insights", { query }));
8558
+ const insights = await client2.get("/insights", { query });
8559
+ return asTextContentResult({ source_note: INSIGHTS_SOURCE_NOTE, insights });
8486
8560
  };
8487
8561
  var get_insights_default = { metadata: metadata88, tool: tool88, handler: handler88 };
8488
8562
 
8563
+ // src/tools/onchain/wallets/get-wallet-balances.ts
8564
+ var WALLET_BALANCE_NETWORKS = [
8565
+ "eth",
8566
+ "base",
8567
+ "bsc",
8568
+ "polygon_pos",
8569
+ "arbitrum",
8570
+ "optimism",
8571
+ "avax",
8572
+ "linea",
8573
+ "stable",
8574
+ "robinhood",
8575
+ "arc"
8576
+ ];
8577
+ var metadata89 = {
8578
+ resource: "onchain.wallets.balances",
8579
+ operation: "read",
8580
+ tags: [
8581
+ "coingecko",
8582
+ "onchain",
8583
+ "wallets",
8584
+ "balances",
8585
+ "portfolio",
8586
+ "geckoterminal"
8587
+ ],
8588
+ httpMethod: "get",
8589
+ httpPath: "/onchain/wallets/{address}/balances",
8590
+ operationId: "wallet-balances-address"
8591
+ };
8592
+ var tool89 = {
8593
+ name: "coingecko_get_onchain_wallet_balances",
8594
+ description: `Get a wallet's token balances with USD values and 24h price change across several EVM networks in one call.
8595
+
8596
+ Args:
8597
+ - address (string, required): EVM wallet address (0x\u2026).
8598
+ - networks (string, optional): Comma-separated GeckoTerminal network ids. Default: all supported (${WALLET_BALANCE_NETWORKS.join(",")}). EVM only; Solana is rejected.
8599
+ - page (integer, optional): Page number. Default 1.
8600
+ - per_page (integer, optional): Tokens per page, 1-100. Default 100.
8601
+
8602
+ Returns:
8603
+ data.attributes with total_value_usd, total_holdings, a per-network value breakdown, and balances sorted by value: network, address, token_type ("native" rows use 0xeeee\u2026eeee), symbol, decimals, balance (decimal string), price_usd, value_usd, h24_price_change_percentage, total_reserve_in_usd (pool liquidity behind the price). Numbers are strings.
8604
+
8605
+ Examples:
8606
+ - All supported networks: {"address":"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"}
8607
+ - Base only: {"address":"0x\u2026","networks":"base"}
8608
+
8609
+ Use when:
8610
+ - You need a priced multi-chain EVM portfolio in one call.
8611
+
8612
+ Don't use when:
8613
+ - The wallet is on Solana (use helius_get_wallet_balances).
8614
+ - You need realized or unrealized PnL (use get_wallet_pnl).
8615
+
8616
+ Notes:
8617
+ - value_usd uses DEX prices; a large value_usd with a small total_reserve_in_usd usually means an illiquid or spam token.`,
8618
+ inputSchema: {
8619
+ type: "object",
8620
+ properties: {
8621
+ address: {
8622
+ type: "string",
8623
+ minLength: 1,
8624
+ maxLength: 100,
8625
+ description: "EVM wallet address (0x\u2026). Required."
8626
+ },
8627
+ networks: {
8628
+ type: "string",
8629
+ maxLength: 200,
8630
+ description: `Comma-separated network ids. Default: ${WALLET_BALANCE_NETWORKS.join(",")}.`
8631
+ },
8632
+ page: {
8633
+ type: "integer",
8634
+ minimum: 1,
8635
+ maximum: 500,
8636
+ description: "Page number, 1-500. Default 1."
8637
+ },
8638
+ per_page: {
8639
+ type: "integer",
8640
+ minimum: 1,
8641
+ maximum: 100,
8642
+ description: "Tokens per page, 1-100. Default 100."
8643
+ }
8644
+ },
8645
+ required: ["address"],
8646
+ additionalProperties: false
8647
+ },
8648
+ annotations: VOLATILE_READ_ANNOTATIONS
8649
+ };
8650
+ var handler89 = async (client2, args) => {
8651
+ const address = String(args?.address ?? "").trim();
8652
+ const query = {
8653
+ networks: args?.networks ?? WALLET_BALANCE_NETWORKS.join(","),
8654
+ page: args?.page,
8655
+ per_page: args?.per_page ?? 100
8656
+ };
8657
+ return asTextContentResult(
8658
+ await client2.get(
8659
+ `/onchain/wallets/${encodeURIComponent(address)}/balances`,
8660
+ { query }
8661
+ )
8662
+ );
8663
+ };
8664
+ var get_wallet_balances_default = { metadata: metadata89, tool: tool89, handler: handler89 };
8665
+
8666
+ // src/tools/onchain/wallets/get-wallet-pnl.ts
8667
+ var metadata90 = {
8668
+ resource: "onchain.wallets.pnl",
8669
+ operation: "read",
8670
+ tags: [
8671
+ "coingecko",
8672
+ "onchain",
8673
+ "wallets",
8674
+ "pnl",
8675
+ "profitability",
8676
+ "geckoterminal"
8677
+ ],
8678
+ httpMethod: "get",
8679
+ httpPath: "/onchain/wallets/{address}/pnl",
8680
+ operationId: "wallet-pnl-address"
8681
+ };
8682
+ var tool90 = {
8683
+ name: "coingecko_get_onchain_wallet_pnl",
8684
+ description: `Get a wallet's realized and unrealized DEX trading PnL, in total, per network, and per token.
8685
+
8686
+ Args:
8687
+ - address (string, required): Wallet address.
8688
+ - networks (string, optional): Comma-separated GeckoTerminal network ids, all from one VM family (all EVM, or "solana"). Default: ${WALLET_BALANCE_NETWORKS.join(",")}. Any GeckoTerminal network is accepted.
8689
+ - page (integer, optional): Page number. Default 1.
8690
+ - per_page (integer, optional): Tokens per page, 1-300. Default 50.
8691
+
8692
+ Returns:
8693
+ data.attributes with total_realized_pnl_usd, total_unrealized_pnl_usd, total_tokens, a per-network breakdown, and token_stats rows: network, address, symbol, realized_pnl_usd, unrealized_pnl_usd, average_buy_price_usd, average_sell_price_usd, total_buy/sell_count, total_buy/sell_usd. Numbers are strings; unrealized_pnl_usd is null when the token is no longer held.
8694
+
8695
+ Examples:
8696
+ - EVM PnL: {"address":"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"}
8697
+ - Solana PnL: {"address":"<base58>","networks":"solana"}
8698
+
8699
+ Use when:
8700
+ - You need to know whether a wallet trades profitably, or which tokens made or lost it money.
8701
+
8702
+ Don't use when:
8703
+ - You need current holdings (use get_wallet_balances).
8704
+ - You need the individual trades (use get_wallet_trades).
8705
+
8706
+ Notes:
8707
+ - PnL is computed from DEX trades CoinGecko indexes; CEX activity and OTC transfers are not counted.`,
8708
+ inputSchema: {
8709
+ type: "object",
8710
+ properties: {
8711
+ address: {
8712
+ type: "string",
8713
+ minLength: 1,
8714
+ maxLength: 100,
8715
+ description: "Wallet address. Required."
8716
+ },
8717
+ networks: {
8718
+ type: "string",
8719
+ maxLength: 200,
8720
+ description: `Comma-separated network ids from one VM family. Default: ${WALLET_BALANCE_NETWORKS.join(",")}.`
8721
+ },
8722
+ page: {
8723
+ type: "integer",
8724
+ minimum: 1,
8725
+ maximum: 500,
8726
+ description: "Page number, 1-500. Default 1."
8727
+ },
8728
+ per_page: {
8729
+ type: "integer",
8730
+ minimum: 1,
8731
+ maximum: 300,
8732
+ description: "Tokens per page, 1-300. Default 50."
8733
+ }
8734
+ },
8735
+ required: ["address"],
8736
+ additionalProperties: false
8737
+ },
8738
+ annotations: VOLATILE_READ_ANNOTATIONS
8739
+ };
8740
+ var handler90 = async (client2, args) => {
8741
+ const address = String(args?.address ?? "").trim();
8742
+ const query = {
8743
+ networks: args?.networks ?? WALLET_BALANCE_NETWORKS.join(","),
8744
+ page: args?.page,
8745
+ per_page: args?.per_page ?? 50
8746
+ };
8747
+ return asTextContentResult(
8748
+ await client2.get(`/onchain/wallets/${encodeURIComponent(address)}/pnl`, {
8749
+ query
8750
+ })
8751
+ );
8752
+ };
8753
+ var get_wallet_pnl_default = { metadata: metadata90, tool: tool90, handler: handler90 };
8754
+
8755
+ // src/tools/onchain/networks/wallets/get-wallet-trades.ts
8756
+ function walletWindowError(args) {
8757
+ if (args?.from == null === (args?.to == null)) return null;
8758
+ return asTextContentResult(
8759
+ {
8760
+ error: "Invalid arguments: pass both from and to (YYYY-MM-DD, at most 30 days apart), or neither for the last 7 days.",
8761
+ code: "VALIDATION_ERROR"
8762
+ },
8763
+ true
8764
+ );
8765
+ }
8766
+ var WALLET_WINDOW_PROPERTIES = {
8767
+ from: {
8768
+ type: "string",
8769
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
8770
+ description: "Window start, YYYY-MM-DD. Send with to; window at most 30 days. Default: last 7 days."
8771
+ },
8772
+ to: {
8773
+ type: "string",
8774
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
8775
+ description: "Window end, YYYY-MM-DD. Send with from."
8776
+ },
8777
+ cursor: {
8778
+ type: "string",
8779
+ maxLength: 500,
8780
+ description: "meta.next_cursor from the previous page. Omit for the first page."
8781
+ },
8782
+ per_page: {
8783
+ type: "integer",
8784
+ minimum: 1,
8785
+ maximum: 300,
8786
+ description: "Rows per page, 1-300. Default 100."
8787
+ }
8788
+ };
8789
+ var metadata91 = {
8790
+ resource: "onchain.networks.wallets.trades",
8791
+ operation: "read",
8792
+ tags: ["coingecko", "onchain", "wallets", "trades", "swaps", "geckoterminal"],
8793
+ httpMethod: "get",
8794
+ httpPath: "/onchain/networks/{network}/wallets/{address}/trades",
8795
+ operationId: "wallet-trades-address"
8796
+ };
8797
+ var tool91 = {
8798
+ name: "coingecko_get_onchain_wallet_trades",
8799
+ description: `Get a wallet's DEX buys and sells on one network, with USD prices at execution time.
8800
+
8801
+ Args:
8802
+ - network (string, required): GeckoTerminal network id, e.g. "eth", "base", "solana". Any GeckoTerminal network.
8803
+ - address (string, required): Wallet address.
8804
+ - from, to (string, optional): YYYY-MM-DD window, sent together, at most 30 days. Default: last 7 days.
8805
+ - cursor (string, optional): meta.next_cursor from the previous page.
8806
+ - per_page (integer, optional): 1-300. Default 100.
8807
+
8808
+ Returns:
8809
+ data rows with pool_address, pool_dex, kind (buy/sell), from/to_token_address, from/to_token_amount, price_from/to_in_usd, volume_in_usd, tx_hash, block_timestamp; meta.next_cursor (null on the last page). An empty data array means no DEX trades in the window.
8810
+
8811
+ Examples:
8812
+ - Last 7 days on Base: {"network":"base","address":"0x\u2026"}
8813
+ - September on Ethereum: {"network":"eth","address":"0x\u2026","from":"2026-09-01","to":"2026-09-30"}
8814
+
8815
+ Use when:
8816
+ - You need what a wallet bought and sold, and at what price.
8817
+
8818
+ Don't use when:
8819
+ - You need all token movements including plain transfers (use get_wallet_transfers).
8820
+ - You need the PnL summary (use get_wallet_pnl).`,
8821
+ inputSchema: {
8822
+ type: "object",
8823
+ properties: {
8824
+ network: {
8825
+ type: "string",
8826
+ minLength: 1,
8827
+ maxLength: 60,
8828
+ description: 'GeckoTerminal network id, e.g. "eth". Required.'
8829
+ },
8830
+ address: {
8831
+ type: "string",
8832
+ minLength: 1,
8833
+ maxLength: 100,
8834
+ description: "Wallet address. Required."
8835
+ },
8836
+ ...WALLET_WINDOW_PROPERTIES
8837
+ },
8838
+ required: ["network", "address"],
8839
+ additionalProperties: false
8840
+ },
8841
+ annotations: VOLATILE_READ_ANNOTATIONS
8842
+ };
8843
+ var handler91 = async (client2, args) => {
8844
+ const invalid2 = walletWindowError(args);
8845
+ if (invalid2) return invalid2;
8846
+ const network = encodeURIComponent(String(args?.network ?? "").trim());
8847
+ const address = encodeURIComponent(String(args?.address ?? "").trim());
8848
+ const query = {
8849
+ from: args?.from,
8850
+ to: args?.to,
8851
+ cursor: args?.cursor,
8852
+ per_page: args?.per_page ?? 100
8853
+ };
8854
+ return asTextContentResult(
8855
+ await client2.get(`/onchain/networks/${network}/wallets/${address}/trades`, {
8856
+ query
8857
+ })
8858
+ );
8859
+ };
8860
+ var get_wallet_trades_default = { metadata: metadata91, tool: tool91, handler: handler91 };
8861
+
8862
+ // src/tools/onchain/networks/wallets/get-wallet-transfers.ts
8863
+ var metadata92 = {
8864
+ resource: "onchain.networks.wallets.transfers",
8865
+ operation: "read",
8866
+ tags: ["coingecko", "onchain", "wallets", "transfers", "geckoterminal"],
8867
+ httpMethod: "get",
8868
+ httpPath: "/onchain/networks/{network}/wallets/{address}/transfers",
8869
+ operationId: "wallet-transfers-address"
8870
+ };
8871
+ var tool92 = {
8872
+ name: "coingecko_get_onchain_wallet_transfers",
8873
+ description: `Get a wallet's inbound and outbound token transfers on one EVM network.
8874
+
8875
+ Args:
8876
+ - network (string, required): One of ${WALLET_BALANCE_NETWORKS.join(", ")}.
8877
+ - address (string, required): EVM wallet address (0x\u2026).
8878
+ - direction (string, optional): "in" or "out". Default both.
8879
+ - token (string, optional): Only transfers of this token contract address.
8880
+ - from, to (string, optional): YYYY-MM-DD window, sent together, at most 30 days. Default: last 7 days.
8881
+ - cursor (string, optional): meta.next_cursor from the previous page.
8882
+ - per_page (integer, optional): 1-300. Default 100.
8883
+
8884
+ Returns:
8885
+ data rows with block_number, tx_hash, direction, token_address, symbol, decimals, amount (decimal string), from_address, to_address, block_timestamp; meta.next_cursor (null on the last page).
8886
+
8887
+ Examples:
8888
+ - Recent inbound on Ethereum: {"network":"eth","address":"0x\u2026","direction":"in"}
8889
+
8890
+ Use when:
8891
+ - You need who sent a wallet what, or where its tokens went.
8892
+
8893
+ Don't use when:
8894
+ - The wallet is on Solana (use helius_get_wallet_transfers).
8895
+ - You only need DEX buys and sells (use get_wallet_trades).`,
8896
+ inputSchema: {
8897
+ type: "object",
8898
+ properties: {
8899
+ network: {
8900
+ type: "string",
8901
+ minLength: 1,
8902
+ maxLength: 60,
8903
+ description: 'Network id, e.g. "eth". Required.'
8904
+ },
8905
+ address: {
8906
+ type: "string",
8907
+ minLength: 1,
8908
+ maxLength: 100,
8909
+ description: "EVM wallet address. Required."
8910
+ },
8911
+ direction: {
8912
+ type: "string",
8913
+ enum: ["in", "out"],
8914
+ description: "Transfer direction. Default both."
8915
+ },
8916
+ token: {
8917
+ type: "string",
8918
+ maxLength: 100,
8919
+ description: "Token contract address filter."
8920
+ },
8921
+ ...WALLET_WINDOW_PROPERTIES
8922
+ },
8923
+ required: ["network", "address"],
8924
+ additionalProperties: false
8925
+ },
8926
+ annotations: VOLATILE_READ_ANNOTATIONS
8927
+ };
8928
+ var handler92 = async (client2, args) => {
8929
+ const invalid2 = walletWindowError(args);
8930
+ if (invalid2) return invalid2;
8931
+ const network = encodeURIComponent(String(args?.network ?? "").trim());
8932
+ const address = encodeURIComponent(String(args?.address ?? "").trim());
8933
+ const query = {
8934
+ direction: args?.direction,
8935
+ token: args?.token,
8936
+ from: args?.from,
8937
+ to: args?.to,
8938
+ cursor: args?.cursor,
8939
+ per_page: args?.per_page ?? 100
8940
+ };
8941
+ return asTextContentResult(
8942
+ await client2.get(
8943
+ `/onchain/networks/${network}/wallets/${address}/transfers`,
8944
+ { query }
8945
+ )
8946
+ );
8947
+ };
8948
+ var get_wallet_transfers_default = { metadata: metadata92, tool: tool92, handler: handler92 };
8949
+
8489
8950
  // src/tools/rwas/query.ts
8490
8951
  function pickQuery(args, keys) {
8491
8952
  const query = {};
@@ -8522,7 +8983,7 @@ var PRECISION_VALUES = [
8522
8983
  ];
8523
8984
 
8524
8985
  // src/tools/rwas/get-rwas-list.ts
8525
- var metadata89 = {
8986
+ var metadata93 = {
8526
8987
  resource: "rwas",
8527
8988
  operation: "read",
8528
8989
  tags: ["coingecko", "rwa", "tokenized", "stocks", "commodities", "etf", "list"],
@@ -8530,7 +8991,7 @@ var metadata89 = {
8530
8991
  httpPath: "/rwas/list",
8531
8992
  operationId: "rwas-list"
8532
8993
  };
8533
- var tool89 = {
8994
+ var tool93 = {
8534
8995
  name: "coingecko_get_rwa_list",
8535
8996
  description: `List every tokenized real-world asset (RWA) that CoinGecko tracks: tokenized stocks, commodities, and ETFs with spot market data. Use it to resolve the RWA asset IDs the other coingecko_get_rwa_* tools take.
8536
8997
 
@@ -8577,15 +9038,15 @@ Plan and freshness:
8577
9038
  },
8578
9039
  annotations: READ_ONLY_ANNOTATIONS
8579
9040
  };
8580
- var handler89 = async (client2, args) => {
9041
+ var handler93 = async (client2, args) => {
8581
9042
  return asTextContentResult(
8582
9043
  await client2.get("/rwas/list", { query: pickQuery(args, ["asset_type"]) })
8583
9044
  );
8584
9045
  };
8585
- var get_rwas_list_default = { metadata: metadata89, tool: tool89, handler: handler89 };
9046
+ var get_rwas_list_default = { metadata: metadata93, tool: tool93, handler: handler93 };
8586
9047
 
8587
9048
  // src/tools/rwas/get-rwas-markets.ts
8588
- var metadata90 = {
9049
+ var metadata94 = {
8589
9050
  resource: "rwas.markets",
8590
9051
  operation: "read",
8591
9052
  tags: ["coingecko", "rwa", "tokenized", "stocks", "commodities", "etf", "markets", "price"],
@@ -8606,7 +9067,7 @@ var QUERY_KEYS = [
8606
9067
  "price_change_percentage",
8607
9068
  "sparkline"
8608
9069
  ];
8609
- var tool90 = {
9070
+ var tool94 = {
8610
9071
  name: "coingecko_get_rwa_markets",
8611
9072
  description: `Get CoinGecko spot market data for tokenized real-world assets (RWAs): tokenized stocks, commodities, and ETFs, with USD price, market cap, and 24h volume aggregated across every token that wraps each asset. This is spot data for the tokenized versions, not the traditional exchange quote and not perpetual futures.
8612
9073
 
@@ -8740,15 +9201,15 @@ Plan and freshness:
8740
9201
  },
8741
9202
  annotations: VOLATILE_READ_ANNOTATIONS
8742
9203
  };
8743
- var handler90 = async (client2, args) => {
9204
+ var handler94 = async (client2, args) => {
8744
9205
  return asTextContentResult(
8745
9206
  await client2.get("/rwas/markets", { query: pickQuery(args, QUERY_KEYS) })
8746
9207
  );
8747
9208
  };
8748
- var get_rwas_markets_default = { metadata: metadata90, tool: tool90, handler: handler90 };
9209
+ var get_rwas_markets_default = { metadata: metadata94, tool: tool94, handler: handler94 };
8749
9210
 
8750
9211
  // src/tools/rwas/get-rwas-asset.ts
8751
- var metadata91 = {
9212
+ var metadata95 = {
8752
9213
  resource: "rwas",
8753
9214
  operation: "read",
8754
9215
  tags: ["coingecko", "rwa", "tokenized", "stocks", "commodities", "etf", "details", "tokens"],
@@ -8757,7 +9218,7 @@ var metadata91 = {
8757
9218
  operationId: "rwas-id"
8758
9219
  };
8759
9220
  var QUERY_KEYS2 = ["sparkline", "tokenized_market_data", "tokens"];
8760
- var tool91 = {
9221
+ var tool95 = {
8761
9222
  name: "coingecko_get_rwa_asset",
8762
9223
  description: `Get one tokenized real-world asset (RWA) from CoinGecko by ID: a tokenized stock, commodity, or ETF, with its metadata, aggregated spot market data, and the list of tokens that wrap it, each with contract addresses per chain and its issuer.
8763
9224
 
@@ -8834,17 +9295,17 @@ Plan and freshness:
8834
9295
  },
8835
9296
  annotations: VOLATILE_READ_ANNOTATIONS
8836
9297
  };
8837
- var handler91 = async (client2, args) => {
9298
+ var handler95 = async (client2, args) => {
8838
9299
  return asTextContentResult(
8839
9300
  await client2.get(`/rwas/${pathId(args)}`, {
8840
9301
  query: pickQuery(args, QUERY_KEYS2)
8841
9302
  })
8842
9303
  );
8843
9304
  };
8844
- var get_rwas_asset_default = { metadata: metadata91, tool: tool91, handler: handler91 };
9305
+ var get_rwas_asset_default = { metadata: metadata95, tool: tool95, handler: handler95 };
8845
9306
 
8846
9307
  // src/tools/rwas/get-rwas-tickers.ts
8847
- var metadata92 = {
9308
+ var metadata96 = {
8848
9309
  resource: "rwas.tickers",
8849
9310
  operation: "read",
8850
9311
  tags: ["coingecko", "rwa", "tokenized", "stocks", "commodities", "etf", "tickers", "exchanges", "liquidity"],
@@ -8860,7 +9321,7 @@ var QUERY_KEYS3 = [
8860
9321
  "order",
8861
9322
  "page"
8862
9323
  ];
8863
- var tool92 = {
9324
+ var tool96 = {
8864
9325
  name: "coingecko_get_rwa_tickers",
8865
9326
  description: `Get exchange tickers for one tokenized real-world asset (RWA) on CoinGecko: every spot pair, across centralized and decentralized exchanges, for the tokens that wrap a given stock, commodity, or ETF. Requires a CoinGecko Basic plan or above.
8866
9327
 
@@ -8950,17 +9411,17 @@ Plan and freshness:
8950
9411
  },
8951
9412
  annotations: VOLATILE_READ_ANNOTATIONS
8952
9413
  };
8953
- var handler92 = async (client2, args) => {
9414
+ var handler96 = async (client2, args) => {
8954
9415
  return asTextContentResult(
8955
9416
  await client2.get(`/rwas/${pathId(args)}/tickers`, {
8956
9417
  query: pickQuery(args, QUERY_KEYS3)
8957
9418
  })
8958
9419
  );
8959
9420
  };
8960
- var get_rwas_tickers_default = { metadata: metadata92, tool: tool92, handler: handler92 };
9421
+ var get_rwas_tickers_default = { metadata: metadata96, tool: tool96, handler: handler96 };
8961
9422
 
8962
9423
  // src/tools/rwas/get-rwas-market-chart.ts
8963
- var metadata93 = {
9424
+ var metadata97 = {
8964
9425
  resource: "rwas.market_chart",
8965
9426
  operation: "read",
8966
9427
  tags: ["coingecko", "rwa", "tokenized", "stocks", "commodities", "etf", "chart", "historical"],
@@ -8969,7 +9430,7 @@ var metadata93 = {
8969
9430
  operationId: "rwas-id-market-chart"
8970
9431
  };
8971
9432
  var QUERY_KEYS4 = ["days", "interval", "precision"];
8972
- var tool93 = {
9433
+ var tool97 = {
8973
9434
  name: "coingecko_get_rwa_market_chart",
8974
9435
  description: `Get historical spot price, market cap, and volume series for one tokenized real-world asset (RWA) on CoinGecko: a tokenized stock, commodity, or ETF, aggregated across the tokens that wrap it. Requires a CoinGecko Basic plan or above.
8975
9436
 
@@ -9037,17 +9498,17 @@ Plan and freshness:
9037
9498
  },
9038
9499
  annotations: HISTORICAL_ANNOTATIONS
9039
9500
  };
9040
- var handler93 = async (client2, args) => {
9501
+ var handler97 = async (client2, args) => {
9041
9502
  const query = pickQuery(args, QUERY_KEYS4);
9042
9503
  if (query.days === void 0) query.days = "1";
9043
9504
  return asTextContentResult(
9044
9505
  await client2.get(`/rwas/${pathId(args)}/market_chart`, { query })
9045
9506
  );
9046
9507
  };
9047
- var get_rwas_market_chart_default = { metadata: metadata93, tool: tool93, handler: handler93 };
9508
+ var get_rwas_market_chart_default = { metadata: metadata97, tool: tool97, handler: handler97 };
9048
9509
 
9049
9510
  // src/tools/rwas/get-rwas-issuers.ts
9050
- var metadata94 = {
9511
+ var metadata98 = {
9051
9512
  resource: "rwas.issuers",
9052
9513
  operation: "read",
9053
9514
  tags: ["coingecko", "rwa", "tokenized", "issuers", "list"],
@@ -9055,7 +9516,7 @@ var metadata94 = {
9055
9516
  httpPath: "/rwas/issuers/list",
9056
9517
  operationId: "rwas-issuers-list"
9057
9518
  };
9058
- var tool94 = {
9519
+ var tool98 = {
9059
9520
  name: "coingecko_get_rwa_issuers",
9060
9521
  description: `List every issuer of tokenized real-world assets (RWAs) that CoinGecko tracks with spot market data: the companies and protocols that mint tokenized stocks, commodities, and ETFs, such as Ondo, Backed, Paxos, Dinari, and Robinhood. Use it to resolve issuer IDs for coingecko_get_rwa_issuer and for the issuer filters on coingecko_get_rwa_markets and coingecko_get_rwa_tickers.
9061
9522
 
@@ -9093,13 +9554,13 @@ Plan and freshness:
9093
9554
  },
9094
9555
  annotations: READ_ONLY_ANNOTATIONS
9095
9556
  };
9096
- var handler94 = async (client2, _args) => {
9557
+ var handler98 = async (client2, _args) => {
9097
9558
  return asTextContentResult(await client2.get("/rwas/issuers/list"));
9098
9559
  };
9099
- var get_rwas_issuers_default = { metadata: metadata94, tool: tool94, handler: handler94 };
9560
+ var get_rwas_issuers_default = { metadata: metadata98, tool: tool98, handler: handler98 };
9100
9561
 
9101
9562
  // src/tools/rwas/get-rwas-issuer.ts
9102
- var metadata95 = {
9563
+ var metadata99 = {
9103
9564
  resource: "rwas.issuers",
9104
9565
  operation: "read",
9105
9566
  tags: ["coingecko", "rwa", "tokenized", "issuers", "details", "tokens"],
@@ -9107,7 +9568,7 @@ var metadata95 = {
9107
9568
  httpPath: "/rwas/issuers/{id}",
9108
9569
  operationId: "rwas-issuers-id"
9109
9570
  };
9110
- var tool95 = {
9571
+ var tool99 = {
9111
9572
  name: "coingecko_get_rwa_issuer",
9112
9573
  description: `Get one issuer of tokenized real-world assets (RWAs) from CoinGecko by ID, with its aggregated spot market cap and 24h volume across every tokenized stock, commodity, and ETF it has minted, plus the full token catalog with per-chain contract addresses.
9113
9574
 
@@ -9167,12 +9628,12 @@ Plan and freshness:
9167
9628
  },
9168
9629
  annotations: VOLATILE_READ_ANNOTATIONS
9169
9630
  };
9170
- var handler95 = async (client2, args) => {
9631
+ var handler99 = async (client2, args) => {
9171
9632
  return asTextContentResult(
9172
9633
  await client2.get(`/rwas/issuers/${pathId(args)}`)
9173
9634
  );
9174
9635
  };
9175
- var get_rwas_issuer_default = { metadata: metadata95, tool: tool95, handler: handler95 };
9636
+ var get_rwas_issuer_default = { metadata: metadata99, tool: tool99, handler: handler99 };
9176
9637
 
9177
9638
  // src/db/supabase.ts
9178
9639
  import { createClient } from "@supabase/supabase-js";
@@ -9341,24 +9802,24 @@ var secureLogger = {
9341
9802
  /**
9342
9803
  * Log authentication events without exposing tokens
9343
9804
  */
9344
- authEvent: (event, success, metadata100) => {
9805
+ authEvent: (event, success, metadata104) => {
9345
9806
  const logData = {
9346
9807
  event,
9347
9808
  success,
9348
9809
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9349
- ...sanitizeObject(metadata100 || {})
9810
+ ...sanitizeObject(metadata104 || {})
9350
9811
  };
9351
9812
  console.error(formatLogMessage("AUTH", event, logData));
9352
9813
  },
9353
9814
  /**
9354
9815
  * Log MCP tool calls for analytics
9355
9816
  */
9356
- toolCall: (toolName, success, durationMs, metadata100) => {
9817
+ toolCall: (toolName, success, durationMs, metadata104) => {
9357
9818
  const logData = {
9358
9819
  tool: toolName,
9359
9820
  success,
9360
9821
  durationMs,
9361
- ...sanitizeObject(metadata100 || {})
9822
+ ...sanitizeObject(metadata104 || {})
9362
9823
  };
9363
9824
  console.error(
9364
9825
  formatLogMessage(
@@ -9371,12 +9832,12 @@ var secureLogger = {
9371
9832
  /**
9372
9833
  * Log request/response for debugging (sanitized)
9373
9834
  */
9374
- request: (method, path, statusCode, metadata100) => {
9835
+ request: (method, path, statusCode, metadata104) => {
9375
9836
  const logData = {
9376
9837
  method,
9377
9838
  path,
9378
9839
  statusCode,
9379
- ...sanitizeObject(metadata100 || {})
9840
+ ...sanitizeObject(metadata104 || {})
9380
9841
  };
9381
9842
  console.error(formatLogMessage("REQUEST", `${method} ${path}`, logData));
9382
9843
  }
@@ -10018,8 +10479,8 @@ function canonicalJson2(value) {
10018
10479
  }
10019
10480
  return JSON.stringify(value) ?? "null";
10020
10481
  }
10021
- function cleanMonitorMetadata(metadata100) {
10022
- const cleaned = { ...metadata100 ?? {} };
10482
+ function cleanMonitorMetadata(metadata104) {
10483
+ const cleaned = { ...metadata104 ?? {} };
10023
10484
  delete cleaned.idempotency_key;
10024
10485
  delete cleaned.idempotency_fingerprint;
10025
10486
  delete cleaned.legacy_duplicate_idempotency_key;
@@ -10055,15 +10516,15 @@ function monitorCreateFingerprint(input) {
10055
10516
  ).digest("hex");
10056
10517
  }
10057
10518
  function existingMonitorFingerprint(existing) {
10058
- const metadata100 = existing.metadata ?? {};
10059
- const stored = metadata100.idempotency_fingerprint;
10519
+ const metadata104 = existing.metadata ?? {};
10520
+ const stored = metadata104.idempotency_fingerprint;
10060
10521
  if (typeof stored === "string" && /^[a-f0-9]{64}$/.test(stored)) {
10061
10522
  return stored;
10062
10523
  }
10063
10524
  return monitorCreateFingerprint({
10064
10525
  cadence: existing.cadence,
10065
10526
  kind: existing.kind,
10066
- metadata: metadata100,
10527
+ metadata: metadata104,
10067
10528
  name: existing.name,
10068
10529
  nextRunAt: existing.next_run_at,
10069
10530
  rules: existing.rules,
@@ -10833,7 +11294,7 @@ var hiveCreateMonitor = {
10833
11294
  const target = parseMonitorTarget(args?.target);
10834
11295
  const rules = parseMonitorRules(args?.rules);
10835
11296
  const status = parseMonitorStatus(args?.status);
10836
- const metadata100 = parseMonitorMetadata(args?.metadata);
11297
+ const metadata104 = parseMonitorMetadata(args?.metadata);
10837
11298
  const name = stringArg(args, "name");
10838
11299
  if (!name) {
10839
11300
  return result(
@@ -10881,7 +11342,7 @@ var hiveCreateMonitor = {
10881
11342
  "monitor"
10882
11343
  );
10883
11344
  }
10884
- if (args?.metadata !== void 0 && !metadata100) {
11345
+ if (args?.metadata !== void 0 && !metadata104) {
10885
11346
  return result(
10886
11347
  {
10887
11348
  ok: false,
@@ -10913,7 +11374,7 @@ var hiveCreateMonitor = {
10913
11374
  cadence: stringArg(args, "cadence"),
10914
11375
  status,
10915
11376
  nextRunAt: stringArg(args, "next_run_at") ?? null,
10916
- metadata: metadata100,
11377
+ metadata: metadata104,
10917
11378
  idempotencyKey
10918
11379
  }),
10919
11380
  "monitor"
@@ -11100,7 +11561,7 @@ var hiveUpdateMonitor = {
11100
11561
  const status = parseMonitorStatus(args?.status);
11101
11562
  const target = args?.target === void 0 ? void 0 : parseMonitorTarget(args?.target);
11102
11563
  const rules = args?.rules === void 0 ? void 0 : parseMonitorRules(args?.rules);
11103
- const metadata100 = args?.metadata === void 0 ? void 0 : parseMonitorMetadata(args?.metadata);
11564
+ const metadata104 = args?.metadata === void 0 ? void 0 : parseMonitorMetadata(args?.metadata);
11104
11565
  if (args?.status !== void 0 && !status) {
11105
11566
  return result(
11106
11567
  {
@@ -11131,7 +11592,7 @@ var hiveUpdateMonitor = {
11131
11592
  "monitor"
11132
11593
  );
11133
11594
  }
11134
- if (args?.metadata !== void 0 && !metadata100) {
11595
+ if (args?.metadata !== void 0 && !metadata104) {
11135
11596
  return result(
11136
11597
  {
11137
11598
  ok: false,
@@ -11152,7 +11613,7 @@ var hiveUpdateMonitor = {
11152
11613
  rules,
11153
11614
  cadence: stringArg(args, "cadence"),
11154
11615
  nextRunAt: args?.next_run_at === void 0 ? void 0 : stringArg(args, "next_run_at") ?? null,
11155
- metadata: metadata100
11616
+ metadata: metadata104
11156
11617
  }),
11157
11618
  "monitor"
11158
11619
  );
@@ -12044,14 +12505,14 @@ function validateFeedbackInput(input) {
12044
12505
  `Invalid arguments: message is ${message.length} characters; the limit is ${FEEDBACK_MAX_MESSAGE_CHARS}.`
12045
12506
  );
12046
12507
  }
12047
- let tool96 = null;
12508
+ let tool100 = null;
12048
12509
  if (input.tool !== void 0 && input.tool !== null) {
12049
12510
  if (typeof input.tool !== "string" || !isToolNameShaped(input.tool)) {
12050
12511
  throw new FeedbackValidationError(
12051
12512
  "Invalid arguments: tool must be a Hive tool name (lowercase letters, digits and underscores, 3 to 128 characters) or omitted."
12052
12513
  );
12053
12514
  }
12054
- tool96 = input.tool;
12515
+ tool100 = input.tool;
12055
12516
  }
12056
12517
  let receiptId = null;
12057
12518
  if (input.receiptId !== void 0 && input.receiptId !== null) {
@@ -12062,7 +12523,7 @@ function validateFeedbackInput(input) {
12062
12523
  }
12063
12524
  receiptId = input.receiptId.toLowerCase();
12064
12525
  }
12065
- return { message: scrubFeedbackText(message), tool: tool96, receiptId };
12526
+ return { message: scrubFeedbackText(message), tool: tool100, receiptId };
12066
12527
  }
12067
12528
  function withTimeout(promise, ms) {
12068
12529
  return new Promise((resolve, reject) => {
@@ -12367,6 +12828,10 @@ function getAllEndpoints() {
12367
12828
  list.push(get_total_supply_chart_default);
12368
12829
  list.push(get_total_supply_chart_range_default);
12369
12830
  list.push(get_insights_default);
12831
+ list.push(get_wallet_balances_default);
12832
+ list.push(get_wallet_pnl_default);
12833
+ list.push(get_wallet_trades_default);
12834
+ list.push(get_wallet_transfers_default);
12370
12835
  list.push(get_rwas_list_default);
12371
12836
  list.push(get_rwas_markets_default);
12372
12837
  list.push(get_rwas_asset_default);
@@ -16573,6 +17038,30 @@ var wrapper = {
16573
17038
  oldToolName: "coingecko_get_coin_insights"
16574
17039
  }
16575
17040
  },
17041
+ {
17042
+ tool: {
17043
+ name: "get_wallet_balances",
17044
+ oldToolName: "coingecko_get_onchain_wallet_balances"
17045
+ }
17046
+ },
17047
+ {
17048
+ tool: {
17049
+ name: "get_wallet_pnl",
17050
+ oldToolName: "coingecko_get_onchain_wallet_pnl"
17051
+ }
17052
+ },
17053
+ {
17054
+ tool: {
17055
+ name: "get_wallet_trades",
17056
+ oldToolName: "coingecko_get_onchain_wallet_trades"
17057
+ }
17058
+ },
17059
+ {
17060
+ tool: {
17061
+ name: "get_wallet_transfers",
17062
+ oldToolName: "coingecko_get_onchain_wallet_transfers"
17063
+ }
17064
+ },
16576
17065
  {
16577
17066
  tool: {
16578
17067
  name: "get_token_lists",
@@ -18117,6 +18606,16 @@ var wrapper = {
18117
18606
  oldToolName: "ccxt_fetch_convert_quote"
18118
18607
  }
18119
18608
  }
18609
+ ],
18610
+ // Blockscout tools describe themselves; these entries only admit the
18611
+ // unprefixed name (src/tools/blockscout/tools.ts).
18612
+ getAllBlockscoutEndpoints: [
18613
+ {
18614
+ tool: {
18615
+ name: "get_token_holder_distribution",
18616
+ oldToolName: "get_token_holder_distribution"
18617
+ }
18618
+ }
18120
18619
  ]
18121
18620
  };
18122
18621
 
@@ -18139,10 +18638,10 @@ function setupLogger() {
18139
18638
  const consoleFormat = winston.format.combine(
18140
18639
  winston.format.colorize(),
18141
18640
  winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
18142
- winston.format.printf(({ timestamp, level, message, ...metadata100 }) => {
18641
+ winston.format.printf(({ timestamp, level, message, ...metadata104 }) => {
18143
18642
  let msg = `${timestamp} [${level}]: ${message}`;
18144
- if (Object.keys(metadata100).length > 0) {
18145
- msg += ` ${JSON.stringify(metadata100)}`;
18643
+ if (Object.keys(metadata104).length > 0) {
18644
+ msg += ` ${JSON.stringify(metadata104)}`;
18146
18645
  }
18147
18646
  return msg;
18148
18647
  })
@@ -26685,10 +27184,10 @@ function setupTools(apiClient) {
26685
27184
  ...derivativesVolumesTools(apiClient),
26686
27185
  ...metadataTools(apiClient)
26687
27186
  ];
26688
- toolCategories.forEach((tool96) => {
26689
- const execute = tool96.execute.bind(tool96);
26690
- tools5[tool96.name] = {
26691
- ...tool96,
27187
+ toolCategories.forEach((tool100) => {
27188
+ const execute = tool100.execute.bind(tool100);
27189
+ tools5[tool100.name] = {
27190
+ ...tool100,
26692
27191
  execute: async (args) => normalizeMcpErrorResult(await execute(args))
26693
27192
  };
26694
27193
  });
@@ -30771,7 +31270,7 @@ var handlers2 = {
30771
31270
  "ccxt_fetch_open_interest_multi"
30772
31271
  )
30773
31272
  };
30774
- var metadata96 = {
31273
+ var metadata100 = {
30775
31274
  fetchMarkets: fetchMarketsMetadata,
30776
31275
  fetchCurrencies: fetchCurrenciesMetadata,
30777
31276
  fetchTicker: fetchTickerMetadata,
@@ -30887,8 +31386,8 @@ var CCXT_REQUIRED_BATCH_FILTERS = {
30887
31386
  ccxt_fetch_borrow_rate_histories: "codes",
30888
31387
  ccxt_fetch_all_greeks: "symbols"
30889
31388
  };
30890
- function withCcxtBounds(tool96) {
30891
- const schema = tool96.inputSchema;
31389
+ function withCcxtBounds(tool100) {
31390
+ const schema = tool100.inputSchema;
30892
31391
  const properties = Object.fromEntries(
30893
31392
  Object.entries(schema.properties || {}).map(
30894
31393
  ([name, rawProperty]) => {
@@ -30907,13 +31406,13 @@ function withCcxtBounds(tool96) {
30907
31406
  )
30908
31407
  );
30909
31408
  const required = new Set(schema.required || []);
30910
- const explicitFilter = CCXT_REQUIRED_BATCH_FILTERS[tool96.name];
31409
+ const explicitFilter = CCXT_REQUIRED_BATCH_FILTERS[tool100.name];
30911
31410
  if (explicitFilter) required.add(explicitFilter);
30912
31411
  return {
30913
- ...tool96,
30914
- description: explicitFilter ? `${tool96.description}
31412
+ ...tool100,
31413
+ description: explicitFilter ? `${tool100.description}
30915
31414
 
30916
- Hive safety bound: ${explicitFilter} is required and accepts at most 100 entries; unbounded all-market reads are rejected.` : tool96.description,
31415
+ Hive safety bound: ${explicitFilter} is required and accepts at most 100 entries; unbounded all-market reads are rejected.` : tool100.description,
30917
31416
  inputSchema: {
30918
31417
  ...schema,
30919
31418
  type: "object",
@@ -30924,10 +31423,10 @@ Hive safety bound: ${explicitFilter} is required and accepts at most 100 entries
30924
31423
  };
30925
31424
  }
30926
31425
  var tools = Object.fromEntries(
30927
- Object.entries(rawTools).map(([key, tool96]) => [key, withCcxtBounds(tool96)])
31426
+ Object.entries(rawTools).map(([key, tool100]) => [key, withCcxtBounds(tool100)])
30928
31427
  );
30929
31428
  var exportedHandlers = handlers2;
30930
- var ccxt_default = { metadata: metadata96, tools, handlers: exportedHandlers };
31429
+ var ccxt_default = { metadata: metadata100, tools, handlers: exportedHandlers };
30931
31430
 
30932
31431
  // src/tools/ccxt/index.ts
30933
31432
  function getAllCCXTEndpoints() {
@@ -31768,7 +32267,7 @@ Use when:
31768
32267
  - You need accurate gas estimation for a complex transaction
31769
32268
 
31770
32269
  Don't use when:
31771
- - You only need current token balances (use moralis_get_wallet_token_balances)
32270
+ - You only need current token balances (use get_wallet_balances)
31772
32271
  - You want historical transaction data (use moralis_get_wallet_history)
31773
32272
  - You need token security analysis without simulation (use goplus_ tools)`,
31774
32273
  inputSchema: {
@@ -31848,7 +32347,7 @@ function handleSimulateTransaction(clientParam, args) {
31848
32347
  "Set TENDERLY_API_KEY, TENDERLY_ACCOUNT_SLUG, and TENDERLY_PROJECT_SLUG environment variables.",
31849
32348
  [
31850
32349
  "goplus_check_token_security \u2014 for token risk analysis without simulation",
31851
- "moralis_get_wallet_token_balances \u2014 for current wallet balances"
32350
+ "get_wallet_balances \u2014 for current wallet balances"
31852
32351
  ],
31853
32352
  {
31854
32353
  network_id: "1",
@@ -31887,7 +32386,7 @@ function handleSimulateTransaction(clientParam, args) {
31887
32386
  "Check that network_id is valid, from/to are valid addresses, and input is properly hex-encoded.",
31888
32387
  [
31889
32388
  "goplus_check_token_security \u2014 for token risk analysis",
31890
- "moralis_get_wallet_token_balances \u2014 for current balances"
32389
+ "get_wallet_balances \u2014 for current balances"
31891
32390
  ],
31892
32391
  {
31893
32392
  network_id: "1",
@@ -32428,7 +32927,7 @@ function handleGetBlockNumber(clientParam, args) {
32428
32927
  );
32429
32928
  }
32430
32929
  function makeGatewayTool(key, name, method, description, properties, required, buildParams) {
32431
- const metadata100 = {
32930
+ const metadata104 = {
32432
32931
  resource: `tenderly.gateway.${method}`,
32433
32932
  operation: "read",
32434
32933
  tags: ["blockchain", "evm", "tenderly", "gateway", "read"],
@@ -32436,7 +32935,7 @@ function makeGatewayTool(key, name, method, description, properties, required, b
32436
32935
  httpPath: "/{network}.gateway.tenderly.co/{accessKey}",
32437
32936
  operationId: `tenderly-${method.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`
32438
32937
  };
32439
- const tool96 = {
32938
+ const tool100 = {
32440
32939
  name,
32441
32940
  title: name.replace(/^tenderly_/, "").replace(/_/g, " ").replace(/\b\w/g, (character) => character.toUpperCase()),
32442
32941
  description: `${description}
@@ -32463,7 +32962,7 @@ Requires TENDERLY_NODE_ACCESS_KEY (or TENDERLY_GATEWAY_ACCESS_KEY). This is a re
32463
32962
  openWorldHint: true
32464
32963
  }
32465
32964
  };
32466
- const handler96 = async (clientParam, args = {}) => {
32965
+ const handler100 = async (clientParam, args = {}) => {
32467
32966
  const client2 = resolveClient(clientParam);
32468
32967
  if (!client2) {
32469
32968
  return createErrorResponse3(
@@ -32493,7 +32992,7 @@ Requires TENDERLY_NODE_ACCESS_KEY (or TENDERLY_GATEWAY_ACCESS_KEY). This is a re
32493
32992
  );
32494
32993
  }
32495
32994
  };
32496
- return { key, tool: tool96, metadata: metadata100, handler: handler96 };
32995
+ return { key, tool: tool100, metadata: metadata104, handler: handler100 };
32497
32996
  }
32498
32997
  var accessListProp = {
32499
32998
  type: "array",
@@ -32900,7 +33399,7 @@ var gatewayDefinitions = [
32900
33399
  }
32901
33400
  )
32902
33401
  ];
32903
- var metadata97 = {
33402
+ var metadata101 = {
32904
33403
  simulateTransaction: simulateTransactionMetadata2,
32905
33404
  simulateBundle: simulateBundleMetadata,
32906
33405
  estimateGas: estimateGasMetadata,
@@ -32927,7 +33426,7 @@ var handlers3 = {
32927
33426
  getBlockNumber: handleGetBlockNumber,
32928
33427
  ...Object.fromEntries(gatewayDefinitions.map((definition) => [definition.key, definition.handler]))
32929
33428
  };
32930
- var tenderly_default = { metadata: metadata97, tools: tools2, handlers: handlers3 };
33429
+ var tenderly_default = { metadata: metadata101, tools: tools2, handlers: handlers3 };
32931
33430
 
32932
33431
  // src/tools/tenderly/index.ts
32933
33432
  function getAllTenderlyEndpoints() {
@@ -40320,7 +40819,7 @@ var newHandlers = {
40320
40819
  }
40321
40820
  }
40322
40821
  };
40323
- function currentMoralisDefinition(key, name, resource, httpMethod, httpPath, description, properties, required, handler96) {
40822
+ function currentMoralisDefinition(key, name, resource, httpMethod, httpPath, description, properties, required, handler100) {
40324
40823
  return {
40325
40824
  key,
40326
40825
  metadata: {
@@ -40348,7 +40847,7 @@ function currentMoralisDefinition(key, name, resource, httpMethod, httpPath, des
40348
40847
  openWorldHint: true
40349
40848
  }
40350
40849
  },
40351
- handler: handler96
40850
+ handler: handler100
40352
40851
  };
40353
40852
  }
40354
40853
  var currentMoralisDefinitions = [
@@ -40577,7 +41076,7 @@ var currentMoralisTools = Object.fromEntries(
40577
41076
  var currentMoralisHandlers = Object.fromEntries(
40578
41077
  currentMoralisDefinitions.map((definition) => [definition.key, definition.handler])
40579
41078
  );
40580
- var metadata98 = {
41079
+ var metadata102 = {
40581
41080
  ...currentMoralisMetadata,
40582
41081
  getTokenScoreTimeseries: getTokenScoreTimeseriesMeta,
40583
41082
  getTokenAnalyticsTimeseries: getTokenAnalyticsTimeseriesMeta,
@@ -40847,8 +41346,8 @@ var MORALIS_ARRAY_LIMITS = {
40847
41346
  getNativeBalancesBatch: { wallet_addresses: 25 },
40848
41347
  getNftCollectionMetadataBatch: { addresses: 25 }
40849
41348
  };
40850
- function withMoralisBounds(key, tool96) {
40851
- const schema = tool96.inputSchema;
41349
+ function withMoralisBounds(key, tool100) {
41350
+ const schema = tool100.inputSchema;
40852
41351
  const properties = Object.fromEntries(
40853
41352
  Object.entries(schema.properties || {}).map(
40854
41353
  ([name, rawProperty]) => {
@@ -40867,7 +41366,7 @@ function withMoralisBounds(key, tool96) {
40867
41366
  )
40868
41367
  );
40869
41368
  return {
40870
- ...tool96,
41369
+ ...tool100,
40871
41370
  inputSchema: {
40872
41371
  ...schema,
40873
41372
  type: "object",
@@ -40877,7 +41376,7 @@ function withMoralisBounds(key, tool96) {
40877
41376
  };
40878
41377
  }
40879
41378
  var activeMetadata = Object.fromEntries(
40880
- activeMoralisKeys.map((key) => [key, metadata98[key]])
41379
+ activeMoralisKeys.map((key) => [key, metadata102[key]])
40881
41380
  );
40882
41381
  var activeTools = Object.fromEntries(
40883
41382
  activeMoralisKeys.map((key) => [key, withMoralisBounds(String(key), tools3[key])])
@@ -41688,7 +42187,7 @@ Use when:
41688
42187
 
41689
42188
  Don't use when:
41690
42189
  - You need raw token account data only (use helius_get_token_balances \u2014 faster)
41691
- - You need EVM wallet data (use moralis_get_wallet_token_balances or moralis_get_wallet_net_worth)`,
42190
+ - You need EVM wallet data (use get_wallet_balances or get_wallet_portfolio)`,
41692
42191
  inputSchema: {
41693
42192
  type: "object",
41694
42193
  properties: {
@@ -44184,7 +44683,7 @@ function meta(resource, path, method) {
44184
44683
  };
44185
44684
  }
44186
44685
  function dataPost(key, name, description, path, properties, bodyBuilder, required = [], schemaExtensions = {}) {
44187
- const tool96 = makeTool(
44686
+ const tool100 = makeTool(
44188
44687
  name,
44189
44688
  description,
44190
44689
  properties,
@@ -44194,7 +44693,7 @@ function dataPost(key, name, description, path, properties, bodyBuilder, require
44194
44693
  );
44195
44694
  return {
44196
44695
  key,
44197
- tool: tool96,
44696
+ tool: tool100,
44198
44697
  metadata: meta(key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`), path, "post"),
44199
44698
  handler: async (clientParam, args = {}) => {
44200
44699
  const client2 = resolveClient4(clientParam);
@@ -44209,7 +44708,7 @@ function dataPost(key, name, description, path, properties, bodyBuilder, require
44209
44708
  };
44210
44709
  }
44211
44710
  function dataGet(key, name, description, path, properties, queryBuilder, required = [], schemaExtensions = {}) {
44212
- const tool96 = makeTool(
44711
+ const tool100 = makeTool(
44213
44712
  name,
44214
44713
  description,
44215
44714
  properties,
@@ -44219,7 +44718,7 @@ function dataGet(key, name, description, path, properties, queryBuilder, require
44219
44718
  );
44220
44719
  return {
44221
44720
  key,
44222
- tool: tool96,
44721
+ tool: tool100,
44223
44722
  metadata: meta(key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`), path, "get"),
44224
44723
  handler: async (clientParam, args = {}) => {
44225
44724
  const client2 = resolveClient4(clientParam);
@@ -44234,7 +44733,7 @@ function dataGet(key, name, description, path, properties, queryBuilder, require
44234
44733
  };
44235
44734
  }
44236
44735
  function nftGet(key, name, description, path, properties, queryBuilder, required = [], annotations = volatileAnnotations3, schemaExtensions = {}) {
44237
- const tool96 = makeTool(
44736
+ const tool100 = makeTool(
44238
44737
  name,
44239
44738
  description,
44240
44739
  properties,
@@ -44244,7 +44743,7 @@ function nftGet(key, name, description, path, properties, queryBuilder, required
44244
44743
  );
44245
44744
  return {
44246
44745
  key,
44247
- tool: tool96,
44746
+ tool: tool100,
44248
44747
  metadata: meta(`nft.${key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`)}`, path, "get"),
44249
44748
  handler: async (clientParam, args = {}) => {
44250
44749
  const client2 = resolveClient4(clientParam);
@@ -44264,7 +44763,7 @@ function nftGet(key, name, description, path, properties, queryBuilder, required
44264
44763
  };
44265
44764
  }
44266
44765
  function nftPost(key, name, description, path, properties, bodyBuilder, required = [], annotations = volatileAnnotations3, schemaExtensions = {}) {
44267
- const tool96 = makeTool(
44766
+ const tool100 = makeTool(
44268
44767
  name,
44269
44768
  description,
44270
44769
  properties,
@@ -44274,7 +44773,7 @@ function nftPost(key, name, description, path, properties, bodyBuilder, required
44274
44773
  );
44275
44774
  return {
44276
44775
  key,
44277
- tool: tool96,
44776
+ tool: tool100,
44278
44777
  metadata: meta(`nft.${key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`)}`, path, "post"),
44279
44778
  handler: async (clientParam, args = {}) => {
44280
44779
  const client2 = resolveClient4(clientParam);
@@ -44294,7 +44793,7 @@ function nftPost(key, name, description, path, properties, bodyBuilder, required
44294
44793
  };
44295
44794
  }
44296
44795
  function rpcTool(key, name, description, method, properties, paramsBuilder, required = [], schemaExtensions = {}, defaultNetwork = "eth-mainnet") {
44297
- const tool96 = makeTool(
44796
+ const tool100 = makeTool(
44298
44797
  name,
44299
44798
  description,
44300
44799
  properties,
@@ -44304,7 +44803,7 @@ function rpcTool(key, name, description, method, properties, paramsBuilder, requ
44304
44803
  );
44305
44804
  return {
44306
44805
  key,
44307
- tool: tool96,
44806
+ tool: tool100,
44308
44807
  metadata: meta(`rpc.${key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`)}`, "/v2/{apiKey}", "post"),
44309
44808
  handler: async (clientParam, args = {}) => {
44310
44809
  const client2 = resolveClient4(clientParam);
@@ -46078,8 +46577,8 @@ var ALCHEMY_ARRAY_LIMITS = {
46078
46577
  networks: 20,
46079
46578
  category: 10
46080
46579
  };
46081
- function withAlchemyBounds(tool96) {
46082
- const schema = tool96.inputSchema;
46580
+ function withAlchemyBounds(tool100) {
46581
+ const schema = tool100.inputSchema;
46083
46582
  const properties = Object.fromEntries(
46084
46583
  Object.entries(schema.properties || {}).map(
46085
46584
  ([name, rawProperty]) => {
@@ -46101,20 +46600,20 @@ function withAlchemyBounds(tool96) {
46101
46600
  }
46102
46601
  )
46103
46602
  );
46104
- return { ...tool96, inputSchema: { ...schema, properties } };
46603
+ return { ...tool100, inputSchema: { ...schema, properties } };
46105
46604
  }
46106
46605
  var activeDefinitions = definitions.map((definition) => ({
46107
46606
  ...definition,
46108
46607
  tool: withAlchemyBounds(definition.tool)
46109
46608
  }));
46110
- var metadata99 = Object.fromEntries(
46609
+ var metadata103 = Object.fromEntries(
46111
46610
  activeDefinitions.map((d) => [d.key, d.metadata])
46112
46611
  );
46113
46612
  var tools4 = Object.fromEntries(activeDefinitions.map((d) => [d.key, d.tool]));
46114
46613
  var handlers5 = Object.fromEntries(
46115
46614
  activeDefinitions.map((d) => [d.key, d.handler])
46116
46615
  );
46117
- var alchemy_default = { metadata: metadata99, tools: tools4, handlers: handlers5 };
46616
+ var alchemy_default = { metadata: metadata103, tools: tools4, handlers: handlers5 };
46118
46617
 
46119
46618
  // src/tools/alchemy/index.ts
46120
46619
  function getAllAlchemyEndpoints() {
@@ -46134,7 +46633,7 @@ function getAllAlchemyEndpoints() {
46134
46633
  import axios3 from "axios";
46135
46634
  var HYPERLIQUID_API_URL = "https://api.hyperliquid.xyz";
46136
46635
  var DEFAULT_LEADERBOARD_LIMIT = 20;
46137
- var MAX_LEADERBOARD_LIMIT = 100;
46636
+ var MAX_LEADERBOARD_LIMIT = 500;
46138
46637
  var FUNDING_HISTORY_PAGE_SIZE = 500;
46139
46638
  var DEFAULT_FUNDING_HISTORY_RETURN_POINTS = 500;
46140
46639
  var MAX_FUNDING_HISTORY_RETURN_POINTS = 500;
@@ -46177,7 +46676,7 @@ var HyperliquidAPI = class {
46177
46676
  * Omit (or pass empty) for the main Hyperliquid DEX.
46178
46677
  * @param limit - top N pairs by 24h notional volume (1-100, default 20).
46179
46678
  */
46180
- async getPerpVolumeLeaderboard(dex, limit = DEFAULT_LEADERBOARD_LIMIT) {
46679
+ async getPerpVolumeLeaderboard(dex, limit = DEFAULT_LEADERBOARD_LIMIT, offset = 0) {
46181
46680
  const body = { type: "metaAndAssetCtxs" };
46182
46681
  if (dex) body.dex = dex;
46183
46682
  const { data } = await this.client.post("/info", body);
@@ -46202,6 +46701,7 @@ var HyperliquidAPI = class {
46202
46701
  Math.max(1, Math.floor(limit || DEFAULT_LEADERBOARD_LIMIT)),
46203
46702
  MAX_LEADERBOARD_LIMIT
46204
46703
  );
46704
+ const safeOffset = Math.max(0, Math.floor(offset || 0));
46205
46705
  return {
46206
46706
  dex: dex || "main",
46207
46707
  pairCount: rows.length,
@@ -46209,7 +46709,8 @@ var HyperliquidAPI = class {
46209
46709
  (sum, row) => sum + row.dayNotionalVolume,
46210
46710
  0
46211
46711
  ),
46212
- leaderboard: rows.slice(0, safeLimit)
46712
+ offset: safeOffset,
46713
+ leaderboard: rows.slice(safeOffset, safeOffset + safeLimit)
46213
46714
  };
46214
46715
  }
46215
46716
  /**
@@ -46438,6 +46939,85 @@ var HyperliquidAPI = class {
46438
46939
  }
46439
46940
  };
46440
46941
 
46942
+ // src/config/responseLimits.ts
46943
+ var RESPONSE_LIMITS = {
46944
+ // Maximum tokens allowed in a response (MCP constraint)
46945
+ MAX_RESPONSE_TOKENS: 25e3,
46946
+ // Estimated characters per token (rough approximation)
46947
+ CHARS_PER_TOKEN: 4,
46948
+ // Maximum characters allowed (25000 * 4 = 100,000)
46949
+ MAX_RESPONSE_CHARS: 1e5,
46950
+ // Default pagination limits for different endpoints
46951
+ DEFAULT_LIMITS: {
46952
+ tokens: 100,
46953
+ pools: 50,
46954
+ trades: 100,
46955
+ holders: 100,
46956
+ events: 200,
46957
+ nfts: 50
46958
+ },
46959
+ // Maximum allowed limits to prevent oversized responses
46960
+ MAX_LIMITS: {
46961
+ tokens: 100,
46962
+ pools: 50,
46963
+ trades: 200,
46964
+ holders: 200,
46965
+ events: 300,
46966
+ nfts: 100
46967
+ },
46968
+ // Size thresholds for automatic truncation (in characters)
46969
+ TRUNCATION_THRESHOLDS: {
46970
+ warning: 8e4,
46971
+ // Start warning at 80KB
46972
+ truncate: 1e5
46973
+ // Force truncation at 100KB
46974
+ }
46975
+ };
46976
+
46977
+ // src/utils/responseHeadroom.ts
46978
+ var MAX_RESULT_SIZE_META_KEY = "anthropic/maxResultSizeChars";
46979
+ var ANNOTATED_MAX_RESULT_SIZE_CHARS = RESPONSE_LIMITS.MAX_RESPONSE_CHARS;
46980
+ var DEFAULT_RESULT_SIZE_GUARD_CHARS = 2e4;
46981
+ var HEADROOM_TOOL_PATTERNS = [
46982
+ // Depth-of-book: order books, l2/l3 books.
46983
+ /orderbook|order_book|l2_book|l3_orderbook/i,
46984
+ // Holder / owner lists.
46985
+ /holders?(?:_|$)|owners_for/i,
46986
+ // OHLCV, candles, charts, and history/time-series families.
46987
+ /ohlc|candle|kline|market_chart|chart|history|timeseries/i
46988
+ ];
46989
+ function toolNeedsResponseHeadroom(toolName) {
46990
+ return HEADROOM_TOOL_PATTERNS.some((pattern) => pattern.test(toolName));
46991
+ }
46992
+ function getAnnotatedMaxResultSizeChars(tool100) {
46993
+ const meta2 = tool100?._meta;
46994
+ const value = meta2?.[MAX_RESULT_SIZE_META_KEY];
46995
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
46996
+ }
46997
+ function withResponseHeadroomMeta(tool100) {
46998
+ if (!toolNeedsResponseHeadroom(tool100.name)) return tool100;
46999
+ const existing = tool100._meta;
47000
+ if (existing && existing[MAX_RESULT_SIZE_META_KEY] !== void 0) return tool100;
47001
+ return {
47002
+ ...tool100,
47003
+ _meta: {
47004
+ ...existing ?? {},
47005
+ [MAX_RESULT_SIZE_META_KEY]: ANNOTATED_MAX_RESULT_SIZE_CHARS
47006
+ }
47007
+ };
47008
+ }
47009
+ function largestFittingRowCount(max, fits) {
47010
+ if (max <= 0 || fits(max)) return Math.max(0, max);
47011
+ let lo = 1;
47012
+ let hi = max - 1;
47013
+ while (lo < hi) {
47014
+ const mid = Math.ceil((lo + hi) / 2);
47015
+ if (fits(mid)) lo = mid;
47016
+ else hi = mid - 1;
47017
+ }
47018
+ return lo;
47019
+ }
47020
+
46441
47021
  // src/tools/hyperliquid/infoTools.ts
46442
47022
  var MAX_ARRAY_ITEMS = 500;
46443
47023
  var SPOT_TOKEN_TTL_MS = 10 * 60 * 1e3;
@@ -47376,14 +47956,19 @@ Hyperliquid is the dominant on-chain perpetual DEX. This reads the venue's own p
47376
47956
 
47377
47957
  Args:
47378
47958
  - 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).
47959
+ - 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.
47960
+ - offset (number, optional): Rank to start from (0 = top pair, default 0).
47380
47961
 
47381
47962
  Returns:
47382
47963
  JSON object:
47383
47964
  {
47384
47965
  "dex": string, // "main" or the builder DEX name
47385
47966
  "pairCount": number, // total pairs on this DEX
47386
- "totalDayNotionalVolume": number, // summed 24h notional volume (USD)
47967
+ "totalDayNotionalVolume": number, // summed 24h notional volume over ALL pairs (USD), not just this page
47968
+ "offset": number, // rank of the first row
47969
+ "returnedCount": number, // rows in this response
47970
+ "next_offset": number | null, // pass as offset for the next page; null after the last pair
47971
+ "pagination_note"?: string, // present when the response budget cut the requested page short
47387
47972
  "leaderboard": [{
47388
47973
  "coin": string, // perp symbol (e.g. "BTC", "HYPE", "SPCX")
47389
47974
  "dayNotionalVolume": number, // 24h notional volume (USD)
@@ -47414,8 +47999,13 @@ Don't use when:
47414
47999
  limit: {
47415
48000
  type: "integer",
47416
48001
  minimum: 1,
47417
- maximum: 100,
47418
- description: "Top N pairs by 24h notional volume (1-100, default 20)."
48002
+ maximum: MAX_LEADERBOARD_LIMIT,
48003
+ 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.`
48004
+ },
48005
+ offset: {
48006
+ type: "integer",
48007
+ minimum: 0,
48008
+ description: "Rank to start from (0 = top pair). Pass the previous response's next_offset for the next page."
47419
48009
  }
47420
48010
  }
47421
48011
  },
@@ -47424,8 +48014,29 @@ Don't use when:
47424
48014
  handler: async (_client, args) => {
47425
48015
  const dex = typeof args?.dex === "string" ? args.dex : void 0;
47426
48016
  const limit = typeof args?.limit === "number" ? args.limit : void 0;
47427
- const data = await api.getPerpVolumeLeaderboard(dex, limit);
47428
- return asTextContentResult(data);
48017
+ const offset = typeof args?.offset === "number" ? args.offset : 0;
48018
+ const data = await api.getPerpVolumeLeaderboard(dex, limit, offset);
48019
+ const requested = data.leaderboard;
48020
+ const page = (kept) => {
48021
+ const next = data.offset + kept;
48022
+ return {
48023
+ ...data,
48024
+ returnedCount: kept,
48025
+ next_offset: next < data.pairCount ? next : null,
48026
+ ...kept < requested.length && {
48027
+ pagination_note: `${kept} of the ${requested.length} requested pairs fit the response budget. Re-call with offset=${next} for the next page.`
48028
+ },
48029
+ leaderboard: requested.slice(0, kept)
48030
+ };
48031
+ };
48032
+ return asTextContentResult(
48033
+ page(
48034
+ largestFittingRowCount(
48035
+ requested.length,
48036
+ (kept) => JSON.stringify(page(kept)).length <= DEFAULT_RESULT_SIZE_GUARD_CHARS - 500
48037
+ )
48038
+ )
48039
+ );
47429
48040
  }
47430
48041
  },
47431
48042
  {
@@ -48599,6 +49210,7 @@ async function collectRwaRows(deps, venues = RWA_VENUE_IDS) {
48599
49210
  // src/tools/rwa/tools.ts
48600
49211
  var DEFAULT_LIMIT = 100;
48601
49212
  var MAX_LIMIT = 500;
49213
+ var PAGE_BUDGET_CHARS = DEFAULT_RESULT_SIZE_GUARD_CHARS - 500;
48602
49214
  var ASSET_CLASSES = [
48603
49215
  "equity",
48604
49216
  "pre_ipo",
@@ -48620,6 +49232,10 @@ function clampLimit2(value) {
48620
49232
  if (!Number.isFinite(parsed)) return DEFAULT_LIMIT;
48621
49233
  return Math.min(Math.max(parsed, 1), MAX_LIMIT);
48622
49234
  }
49235
+ function clampOffset(value) {
49236
+ const parsed = typeof value === "number" ? Math.floor(value) : NaN;
49237
+ return Number.isFinite(parsed) ? Math.max(parsed, 0) : 0;
49238
+ }
48623
49239
  function sortRows(rows) {
48624
49240
  return [...rows].sort((a, b) => {
48625
49241
  const oiA = a.open_interest_usd ?? -1;
@@ -48657,15 +49273,30 @@ function applyFilters(rows, args) {
48657
49273
  }
48658
49274
  };
48659
49275
  }
48660
- function boundedEnvelope(rows, limit) {
49276
+ function boundedPayload(rows, args, render) {
48661
49277
  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
49278
+ const offset = clampOffset(args?.offset);
49279
+ const requested = sorted.slice(offset, offset + clampLimit2(args?.limit));
49280
+ const build = (kept) => {
49281
+ const next = offset + kept;
49282
+ const page = {
49283
+ count: sorted.length,
49284
+ offset,
49285
+ returnedCount: kept,
49286
+ truncated: next < sorted.length,
49287
+ next_offset: next < sorted.length ? next : null,
49288
+ ...kept < requested.length && {
49289
+ pagination_note: `${kept} of the ${requested.length} requested rows fit the response budget. Re-call with offset=${next} for the next page.`
49290
+ }
49291
+ };
49292
+ return render(page, requested.slice(0, kept));
48668
49293
  };
49294
+ return build(
49295
+ largestFittingRowCount(
49296
+ requested.length,
49297
+ (kept) => JSON.stringify(build(kept)).length <= PAGE_BUDGET_CHARS
49298
+ )
49299
+ );
48669
49300
  }
48670
49301
  var FILTER_PARAM_PROPERTIES = {
48671
49302
  underlying: {
@@ -48685,7 +49316,12 @@ var FILTER_PARAM_PROPERTIES = {
48685
49316
  type: "integer",
48686
49317
  minimum: 1,
48687
49318
  maximum: MAX_LIMIT,
48688
- description: `Maximum rows to return (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}). Rows are ordered by open interest (deepest first).`
49319
+ 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.`
49320
+ },
49321
+ offset: {
49322
+ type: "integer",
49323
+ minimum: 0,
49324
+ description: "Rows to skip in the open-interest order (default 0). Pass the previous response's next_offset to read the next page."
48689
49325
  }
48690
49326
  };
48691
49327
  function toMarketRow(row) {
@@ -48733,9 +49369,10 @@ Args:
48733
49369
  - underlying (string, optional): filter to one canonical underlying ticker.
48734
49370
  - asset_class (string, optional): filter to one asset class.
48735
49371
  - limit (integer, optional): max rows (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}), deepest open interest first.
49372
+ - offset (integer, optional): rows to skip; pass next_offset to page.
48736
49373
 
48737
49374
  Returns:
48738
- Bounded JSON envelope: { "venue", "count", "returnedCount", "truncated", "rows": [normalized rows] }. ${returnsNote}
49375
+ 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
49376
 
48740
49377
  Use when:
48741
49378
  ${useWhen.map((line) => ` - ${line}`).join("\n")}
@@ -48747,7 +49384,8 @@ ${dontUseWhen.map((line) => ` - ${line}`).join("\n")}`,
48747
49384
  properties: {
48748
49385
  underlying: FILTER_PARAM_PROPERTIES.underlying,
48749
49386
  asset_class: FILTER_PARAM_PROPERTIES.asset_class,
48750
- limit: FILTER_PARAM_PROPERTIES.limit
49387
+ limit: FILTER_PARAM_PROPERTIES.limit,
49388
+ offset: FILTER_PARAM_PROPERTIES.offset
48751
49389
  }
48752
49390
  },
48753
49391
  annotations: VOLATILE_READ_ANNOTATIONS
@@ -48756,13 +49394,15 @@ ${dontUseWhen.map((line) => ` - ${line}`).join("\n")}`,
48756
49394
  const { rows, note } = await fetchRwaVenueRows(venueId, deps);
48757
49395
  stampPriceSuspect(rows);
48758
49396
  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
- });
49397
+ return asTextContentResult(
49398
+ boundedPayload(filtered, args, (page, pageRows) => ({
49399
+ venue: venueId,
49400
+ ...note ? { note } : {},
49401
+ filters,
49402
+ ...page,
49403
+ rows: pageRows
49404
+ }))
49405
+ );
48766
49406
  }
48767
49407
  };
48768
49408
  }
@@ -48798,6 +49438,7 @@ Args:
48798
49438
  - venue (string, optional): filter to one venue ("ostium", "avantis", "lighter", "extended", "hyperliquid", or "hyperliquid:xyz").
48799
49439
  - asset_class (string, optional): filter to one asset class (equity, pre_ipo, etf, index, commodity, fx, crypto).
48800
49440
  - limit (integer, optional): max rows (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}), deepest open interest first.
49441
+ - offset (integer, optional): rows to skip; pass next_offset to page.
48801
49442
 
48802
49443
  Returns:
48803
49444
  JSON object:
@@ -48805,9 +49446,11 @@ Returns:
48805
49446
  "filters": { "underlying", "venue", "asset_class" },
48806
49447
  "venueCount": number, "okCount": number,
48807
49448
  "venues": [{ "venue", "runtime_status", "rowCount", "error"? }],
48808
- "count": number, "returnedCount": number, "truncated": boolean,
49449
+ "count": number, "offset": number, "returnedCount": number, "truncated": boolean,
49450
+ "next_offset": number | null, "pagination_note"?: string,
48809
49451
  "rows": [normalized funding rows]
48810
49452
  }
49453
+ 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
49454
 
48812
49455
  Use when:
48813
49456
  - "TSLA perp funding across venues" / tokenized-stock or equity perp funding questions
@@ -48827,17 +49470,19 @@ Don't use when:
48827
49470
  handler: async (_client, args) => {
48828
49471
  const { results, rows } = await collectRwaRows(deps);
48829
49472
  const { filtered, filters } = applyFilters(rows, args);
48830
- const envelope = boundedEnvelope(filtered, clampLimit2(args?.limit));
48831
49473
  const okCount = results.filter(
48832
49474
  (result2) => result2.runtime_status === "ok"
48833
49475
  ).length;
48834
- return asTextContentResult({
48835
- filters,
48836
- venueCount: results.length,
48837
- okCount,
48838
- venues: results,
48839
- ...envelope
48840
- });
49476
+ return asTextContentResult(
49477
+ boundedPayload(filtered, args, (page, pageRows) => ({
49478
+ filters,
49479
+ venueCount: results.length,
49480
+ okCount,
49481
+ venues: results,
49482
+ ...page,
49483
+ rows: pageRows
49484
+ }))
49485
+ );
48841
49486
  }
48842
49487
  },
48843
49488
  {
@@ -48864,6 +49509,7 @@ Args:
48864
49509
  - venue (string, optional): filter to one venue.
48865
49510
  - asset_class (string, optional): filter to one asset class (equity, pre_ipo, etf, index, commodity, fx, crypto).
48866
49511
  - limit (integer, optional): max rows (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}), deepest open interest first.
49512
+ - offset (integer, optional): rows to skip; pass next_offset to page.
48867
49513
 
48868
49514
  Returns:
48869
49515
  JSON object:
@@ -48871,7 +49517,7 @@ Returns:
48871
49517
  "filters": {...},
48872
49518
  "venueCount": number, "okCount": number,
48873
49519
  "venues": [{ "venue", "runtime_status", "rowCount", "error"? }],
48874
- "count", "returnedCount", "truncated",
49520
+ "count", "offset", "returnedCount", "truncated", "next_offset", "pagination_note"?,
48875
49521
  "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
49522
  }
48877
49523
 
@@ -48892,20 +49538,19 @@ Don't use when:
48892
49538
  handler: async (_client, args) => {
48893
49539
  const { results, rows } = await collectRwaRows(deps);
48894
49540
  const { filtered, filters } = applyFilters(rows, args);
48895
- const envelope = boundedEnvelope(filtered, clampLimit2(args?.limit));
48896
49541
  const okCount = results.filter(
48897
49542
  (result2) => result2.runtime_status === "ok"
48898
49543
  ).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
- });
49544
+ return asTextContentResult(
49545
+ boundedPayload(filtered, args, (page, pageRows) => ({
49546
+ filters,
49547
+ venueCount: results.length,
49548
+ okCount,
49549
+ venues: results,
49550
+ ...page,
49551
+ rows: pageRows.map(toMarketRow)
49552
+ }))
49553
+ );
48909
49554
  }
48910
49555
  },
48911
49556
  // -----------------------------------------------------------------
@@ -49285,13 +49930,13 @@ function historyInputSchema(baseCoinDescription) {
49285
49930
  required: ["base_coin"]
49286
49931
  };
49287
49932
  }
49288
- function errorResult2(tool96, error) {
49933
+ function errorResult2(tool100, error) {
49289
49934
  if (error instanceof ArchiveInputError) {
49290
49935
  return asTextContentResult(
49291
49936
  {
49292
49937
  error: error.message,
49293
49938
  runtime_status: "invalid_input",
49294
- tool: tool96
49939
+ tool: tool100
49295
49940
  },
49296
49941
  true
49297
49942
  );
@@ -49301,17 +49946,17 @@ function errorResult2(tool96, error) {
49301
49946
  {
49302
49947
  error: message,
49303
49948
  runtime_status: classifyRuntimeStatusFromError(message),
49304
- tool: tool96
49949
+ tool: tool100
49305
49950
  },
49306
49951
  true
49307
49952
  );
49308
49953
  }
49309
- function missingCredentialsResult(tool96) {
49954
+ function missingCredentialsResult(tool100) {
49310
49955
  return asTextContentResult(
49311
49956
  {
49312
49957
  error: ARCHIVE_MISSING_CREDENTIALS_MESSAGE,
49313
49958
  runtime_status: "missing_key",
49314
- tool: tool96
49959
+ tool: tool100
49315
49960
  },
49316
49961
  true
49317
49962
  );
@@ -49685,74 +50330,6 @@ async function fetchPolymarketJson(url, label, timeoutMs = POLYMARKET_TIMEOUT_MS
49685
50330
  }
49686
50331
  }
49687
50332
 
49688
- // src/config/responseLimits.ts
49689
- var RESPONSE_LIMITS = {
49690
- // Maximum tokens allowed in a response (MCP constraint)
49691
- MAX_RESPONSE_TOKENS: 25e3,
49692
- // Estimated characters per token (rough approximation)
49693
- CHARS_PER_TOKEN: 4,
49694
- // Maximum characters allowed (25000 * 4 = 100,000)
49695
- MAX_RESPONSE_CHARS: 1e5,
49696
- // Default pagination limits for different endpoints
49697
- DEFAULT_LIMITS: {
49698
- tokens: 100,
49699
- pools: 50,
49700
- trades: 100,
49701
- holders: 100,
49702
- events: 200,
49703
- nfts: 50
49704
- },
49705
- // Maximum allowed limits to prevent oversized responses
49706
- MAX_LIMITS: {
49707
- tokens: 100,
49708
- pools: 50,
49709
- trades: 200,
49710
- holders: 200,
49711
- events: 300,
49712
- nfts: 100
49713
- },
49714
- // Size thresholds for automatic truncation (in characters)
49715
- TRUNCATION_THRESHOLDS: {
49716
- warning: 8e4,
49717
- // Start warning at 80KB
49718
- truncate: 1e5
49719
- // Force truncation at 100KB
49720
- }
49721
- };
49722
-
49723
- // src/utils/responseHeadroom.ts
49724
- var MAX_RESULT_SIZE_META_KEY = "anthropic/maxResultSizeChars";
49725
- var ANNOTATED_MAX_RESULT_SIZE_CHARS = RESPONSE_LIMITS.MAX_RESPONSE_CHARS;
49726
- var DEFAULT_RESULT_SIZE_GUARD_CHARS = 2e4;
49727
- var HEADROOM_TOOL_PATTERNS = [
49728
- // Depth-of-book: order books, l2/l3 books.
49729
- /orderbook|order_book|l2_book|l3_orderbook/i,
49730
- // Holder / owner lists.
49731
- /holders?(?:_|$)|owners_for/i,
49732
- // OHLCV, candles, charts, and history/time-series families.
49733
- /ohlc|candle|kline|market_chart|chart|history|timeseries/i
49734
- ];
49735
- function toolNeedsResponseHeadroom(toolName) {
49736
- return HEADROOM_TOOL_PATTERNS.some((pattern) => pattern.test(toolName));
49737
- }
49738
- function getAnnotatedMaxResultSizeChars(tool96) {
49739
- const meta2 = tool96?._meta;
49740
- const value = meta2?.[MAX_RESULT_SIZE_META_KEY];
49741
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
49742
- }
49743
- function withResponseHeadroomMeta(tool96) {
49744
- if (!toolNeedsResponseHeadroom(tool96.name)) return tool96;
49745
- const existing = tool96._meta;
49746
- if (existing && existing[MAX_RESULT_SIZE_META_KEY] !== void 0) return tool96;
49747
- return {
49748
- ...tool96,
49749
- _meta: {
49750
- ...existing ?? {},
49751
- [MAX_RESULT_SIZE_META_KEY]: ANNOTATED_MAX_RESULT_SIZE_CHARS
49752
- }
49753
- };
49754
- }
49755
-
49756
50333
  // src/tools/polymarket/api.ts
49757
50334
  var DEFAULT_LIMIT3 = 10;
49758
50335
  var MAX_LIMIT3 = 50;
@@ -49760,9 +50337,14 @@ var MAX_OFFSET = 1e4;
49760
50337
  var DEFAULT_BOOK_DEPTH = 10;
49761
50338
  var MAX_BOOK_DEPTH = 50;
49762
50339
  var MAX_HISTORY_POINTS = 500;
49763
- var SEARCH_SCAN_LIMIT = 100;
50340
+ var SEARCH_EVENT_LIMIT = 20;
49764
50341
  var EVENT_MARKETS_PREVIEW = 10;
49765
- var EVENT_RESPONSE_BUDGET_CHARS = DEFAULT_RESULT_SIZE_GUARD_CHARS;
50342
+ var EVENT_RESPONSE_BUDGET_CHARS = (
50343
+ // Rows only: the okResult envelope (odds note, source URL twice, filters)
50344
+ // adds ~1k, and at the full 20k the guard still trimmed 48 rows to 45
50345
+ // (order=volume, limit=50, 2026-10-09).
50346
+ DEFAULT_RESULT_SIZE_GUARD_CHARS - 2e3
50347
+ );
49766
50348
  var DESCRIPTION_MAX_CHARS = 1200;
49767
50349
  var PRICE_INTERVALS = ["1d", "1w", "1m", "max"];
49768
50350
  var DEFAULT_FIDELITY_MINUTES = {
@@ -49962,6 +50544,21 @@ function parseInterval(value) {
49962
50544
  }
49963
50545
  return interval;
49964
50546
  }
50547
+ var EVENT_ORDERS = [
50548
+ "volume24hr",
50549
+ "volume",
50550
+ "liquidity",
50551
+ "endDate"
50552
+ ];
50553
+ function eventOrderParams(value, now = /* @__PURE__ */ new Date()) {
50554
+ const order = value === void 0 || value === null ? "volume24hr" : value;
50555
+ if (typeof order !== "string" || !EVENT_ORDERS.includes(order)) {
50556
+ invalid(
50557
+ `order must be one of ${EVENT_ORDERS.map((o) => `"${o}"`).join(", ")}; got ${JSON.stringify(value)}.`
50558
+ );
50559
+ }
50560
+ return order === "endDate" ? { order, ascending: true, end_date_min: now.toISOString() } : { order, ascending: false };
50561
+ }
49965
50562
  function parseFidelity(value, interval) {
49966
50563
  return parseBoundedInteger(
49967
50564
  value,
@@ -50290,6 +50887,22 @@ async function fetchGammaEvents(params) {
50290
50887
  const data = await fetchPolymarketJson(url, "Gamma /events");
50291
50888
  return { url, rows: asRecordArray(data, "Gamma /events") };
50292
50889
  }
50890
+ async function searchGammaMarkets(query, closed) {
50891
+ const url = buildUrl(GAMMA_BASE_URL, "/public-search", {
50892
+ q: query,
50893
+ limit_per_type: SEARCH_EVENT_LIMIT,
50894
+ events_status: closed ? "closed" : "active"
50895
+ });
50896
+ const data = asRecord(await fetchPolymarketJson(url, "Gamma /public-search"));
50897
+ const events = asRecordArray(data?.events ?? [], "Gamma /public-search");
50898
+ const rows = events.flatMap(
50899
+ (event) => (Array.isArray(event.markets) ? event.markets : []).map(asRecord).filter((market) => market !== null).map((market) => ({
50900
+ ...market,
50901
+ events: [{ slug: event.slug, title: event.title }]
50902
+ }))
50903
+ );
50904
+ return { url, rows, eventCount: events.length };
50905
+ }
50293
50906
  async function resolveMarket(selector) {
50294
50907
  if (selector.slug) {
50295
50908
  const { url, rows } = await fetchGammaMarkets({ slug: selector.slug });
@@ -50410,13 +51023,13 @@ function okResult(sourceUrl, body) {
50410
51023
  may_have_more
50411
51024
  });
50412
51025
  }
50413
- function errorResult3(tool96, error) {
51026
+ function errorResult3(tool100, error) {
50414
51027
  if (error instanceof PolymarketInputError) {
50415
51028
  return asTextContentResult(
50416
51029
  {
50417
51030
  error: error.message,
50418
51031
  runtime_status: "invalid_input",
50419
- tool: tool96
51032
+ tool: tool100
50420
51033
  },
50421
51034
  true
50422
51035
  );
@@ -50426,7 +51039,7 @@ function errorResult3(tool96, error) {
50426
51039
  {
50427
51040
  error: message,
50428
51041
  runtime_status: classifyRuntimeStatusFromError(message),
50429
- tool: tool96
51042
+ tool: tool100
50430
51043
  },
50431
51044
  true
50432
51045
  );
@@ -50466,7 +51079,7 @@ function getAllPolymarketEndpoints() {
50466
51079
  },
50467
51080
  tool: {
50468
51081
  name: "polymarket_search_markets",
50469
- 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.
51082
+ 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.
50470
51083
 
50471
51084
  Args:
50472
51085
  - query (string, optional): free-text terms, all must match (case-insensitive), e.g. "fed rate" or "bitcoin 2026".
@@ -50477,7 +51090,7 @@ Args:
50477
51090
  Returns:
50478
51091
  ${ENVELOPE_DOC}
50479
51092
  "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" }].
50480
- 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.
51093
+ 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.
50481
51094
 
50482
51095
  Examples:
50483
51096
  - { "query": "fed decision", "limit": 5 } \u2014 Fed-rate brackets with current odds.
@@ -50514,16 +51127,20 @@ Don't use when:
50514
51127
  const limit = parseLimit(args?.limit);
50515
51128
  const active = parseOptionalBoolean(args?.active, "active", true);
50516
51129
  const closed = parseOptionalBoolean(args?.closed, "closed", false);
50517
- const scanLimit = query ? SEARCH_SCAN_LIMIT : limit;
50518
- const { url, rows } = await fetchGammaMarkets({
50519
- limit: scanLimit,
51130
+ const scanLimit = query ? SEARCH_EVENT_LIMIT : limit;
51131
+ const search = query ? await searchGammaMarkets(query, closed) : null;
51132
+ const listing = search ?? await fetchGammaMarkets({
51133
+ limit,
50520
51134
  active,
50521
51135
  closed,
50522
51136
  order: "volume24hr",
50523
51137
  ascending: false
50524
51138
  });
50525
- const mapped = rows.map(toMarketRow2);
50526
- const matched = query ? mapped.filter(
51139
+ const { url, rows } = listing;
51140
+ const mapped = rows.map(toMarketRow2).filter(
51141
+ (row) => !query || row.active === active && row.closed === closed
51142
+ );
51143
+ const exact = query ? mapped.filter(
50527
51144
  (row) => matchesQuery(
50528
51145
  [
50529
51146
  row.question,
@@ -50534,19 +51151,22 @@ Don't use when:
50534
51151
  ],
50535
51152
  query
50536
51153
  )
50537
- ) : mapped;
51154
+ ).sort((a, b) => (b.volume24hr ?? 0) - (a.volume24hr ?? 0)) : mapped;
51155
+ const fuzzy = Boolean(query) && exact.length === 0;
51156
+ const matched = fuzzy ? mapped : exact;
51157
+ const scanned = search ? search.eventCount : rows.length;
50538
51158
  const items = matched.slice(0, limit);
50539
51159
  return okResult(url, {
50540
51160
  filters: { query, active, closed, limit },
50541
51161
  scan: {
50542
- scanned: rows.length,
51162
+ scanned,
50543
51163
  scan_limit: scanLimit,
50544
51164
  matched: matched.length,
50545
- order: "volume24hr desc"
51165
+ order: fuzzy ? "relevance (no market contains every term)" : "volume24hr desc"
50546
51166
  },
50547
51167
  rows: items,
50548
51168
  returnedCount: items.length,
50549
- may_have_more: matched.length > limit || rows.length >= scanLimit
51169
+ may_have_more: matched.length > limit || scanned >= scanLimit
50550
51170
  });
50551
51171
  } catch (error) {
50552
51172
  return errorResult3(toolName, error);
@@ -50568,14 +51188,16 @@ Args:
50568
51188
  - slug (string, optional): one event's URL slug (exact); other filters are ignored.
50569
51189
  - active (boolean, optional): Polymarket active flag filter (default true).
50570
51190
  - closed (boolean, optional): Polymarket closed flag filter (default false).
51191
+ - 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.
50571
51192
  ${PAGING_ARGS_DOC}
50572
51193
 
50573
51194
  Returns:
50574
51195
  ${ENVELOPE_DOC}
50575
- "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.
51196
+ "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.
50576
51197
 
50577
51198
  Examples:
50578
51199
  - { "tag": "crypto", "limit": 5 } \u2014 the busiest crypto events.
51200
+ - { "order": "volume", "limit": 5 } \u2014 the biggest events by all-time volume (not just today's trend).
50579
51201
  - { "slug": "presidential-election-winner-2028" } \u2014 one event with its top-${EVENT_MARKETS_PREVIEW} candidates by odds.
50580
51202
  - { "limit": 10, "offset": 10 } \u2014 the next page of the most active events.
50581
51203
 
@@ -50600,6 +51222,11 @@ Don't use when:
50600
51222
  },
50601
51223
  active: ACTIVE_SCHEMA,
50602
51224
  closed: CLOSED_SCHEMA,
51225
+ order: {
51226
+ type: "string",
51227
+ enum: [...EVENT_ORDERS],
51228
+ description: 'Sort: "volume24hr" (default, trending), "volume" (biggest all-time), "liquidity", or "endDate" (closing soonest from now).'
51229
+ },
50603
51230
  limit: LIMIT_SCHEMA,
50604
51231
  offset: OFFSET_SCHEMA
50605
51232
  }
@@ -50615,19 +51242,19 @@ Don't use when:
50615
51242
  const offset = parseOffset(args?.offset);
50616
51243
  const active = parseOptionalBoolean(args?.active, "active", true);
50617
51244
  const closed = parseOptionalBoolean(args?.closed, "closed", false);
51245
+ const sort = eventOrderParams(args?.order);
50618
51246
  const { url, rows } = slug ? await fetchGammaEvents({ slug }) : await fetchGammaEvents({
50619
51247
  limit,
50620
51248
  offset,
50621
51249
  active,
50622
51250
  closed,
50623
51251
  tag_slug: tag ?? void 0,
50624
- order: "volume24hr",
50625
- ascending: false
51252
+ ...sort
50626
51253
  });
50627
51254
  const bounded = boundEventRows(rows);
50628
51255
  const items = bounded.rows;
50629
51256
  return okResult(url, {
50630
- filters: slug ? { slug } : { tag, active, closed, limit, offset },
51257
+ filters: slug ? { slug } : { tag, active, closed, order: sort.order, limit, offset },
50631
51258
  markets_preview_limit: bounded.marketsPreviewLimit,
50632
51259
  rows_dropped_for_size: bounded.rowsDropped,
50633
51260
  rows: items,
@@ -51039,7 +51666,7 @@ function isAbortError(error) {
51039
51666
  const name = error?.name;
51040
51667
  return name === "AbortError" || name === "TimeoutError";
51041
51668
  }
51042
- async function fetchJsonWithTimeout(url, label, timeoutMs = SIGNALS_FETCH_TIMEOUT_MS) {
51669
+ async function fetchJsonWithTimeout(url, label, timeoutMs = SIGNALS_FETCH_TIMEOUT_MS, headers = {}) {
51043
51670
  const controller = new AbortController();
51044
51671
  const timer = setTimeout(() => controller.abort(), timeoutMs);
51045
51672
  try {
@@ -51049,7 +51676,8 @@ async function fetchJsonWithTimeout(url, label, timeoutMs = SIGNALS_FETCH_TIMEOU
51049
51676
  signal: controller.signal,
51050
51677
  headers: {
51051
51678
  Accept: "application/json",
51052
- "User-Agent": "hive-mcp-server/1.0.0"
51679
+ "User-Agent": "hive-mcp-server/1.0.0",
51680
+ ...headers
51053
51681
  }
51054
51682
  });
51055
51683
  } catch (error) {
@@ -51910,10 +52538,10 @@ async function fetchHackRows() {
51910
52538
  }
51911
52539
 
51912
52540
  // src/tools/signals/tools.ts
51913
- function errorResult4(tool96, error, extra = {}) {
52541
+ function errorResult4(tool100, error, extra = {}) {
51914
52542
  if (error instanceof SignalsInputError) {
51915
52543
  return asTextContentResult(
51916
- { error: error.message, runtime_status: "invalid_input", tool: tool96, ...extra },
52544
+ { error: error.message, runtime_status: "invalid_input", tool: tool100, ...extra },
51917
52545
  true
51918
52546
  );
51919
52547
  }
@@ -51922,7 +52550,7 @@ function errorResult4(tool96, error, extra = {}) {
51922
52550
  {
51923
52551
  error: message,
51924
52552
  runtime_status: classifyRuntimeStatusFromError(message),
51925
- tool: tool96,
52553
+ tool: tool100,
51926
52554
  ...extra
51927
52555
  },
51928
52556
  true
@@ -52294,6 +52922,334 @@ Don't use when:
52294
52922
  ];
52295
52923
  }
52296
52924
 
52925
+ // src/tools/blockscout/tools.ts
52926
+ import Coingecko2 from "@coingecko/coingecko-typescript";
52927
+ var BLOCKSCOUT_PROVIDER = "Blockscout";
52928
+ var HOLDERS_PAGE_SIZE = 50;
52929
+ var DEFAULT_MAX_PAGES = 20;
52930
+ var MAX_PAGES = 40;
52931
+ var TIME_BUDGET_MS = 2e4;
52932
+ var CHAINS = {
52933
+ eth: { chainId: 1, host: "eth.blockscout.com", gecko: "eth" },
52934
+ base: { chainId: 8453, host: "base.blockscout.com", gecko: "base" },
52935
+ optimism: { chainId: 10, host: "explorer.optimism.io", gecko: "optimism" },
52936
+ arbitrum: {
52937
+ chainId: 42161,
52938
+ host: "arbitrum.blockscout.com",
52939
+ gecko: "arbitrum"
52940
+ },
52941
+ polygon: {
52942
+ chainId: 137,
52943
+ host: "polygon.blockscout.com",
52944
+ gecko: "polygon_pos"
52945
+ }
52946
+ };
52947
+ var CHAIN_ALIASES = {
52948
+ ethereum: "eth",
52949
+ mainnet: "eth",
52950
+ "1": "eth",
52951
+ "8453": "base",
52952
+ op: "optimism",
52953
+ "10": "optimism",
52954
+ arb: "arbitrum",
52955
+ "42161": "arbitrum",
52956
+ matic: "polygon",
52957
+ polygon_pos: "polygon",
52958
+ "137": "polygon"
52959
+ };
52960
+ var USD_TIERS = [
52961
+ { label: ">$10k", min_usd: 1e4, max_usd: null },
52962
+ { label: "$1k-$10k", min_usd: 1e3, max_usd: 1e4 },
52963
+ { label: "$100-$1k", min_usd: 100, max_usd: 1e3 },
52964
+ { label: "$1-$100", min_usd: 1, max_usd: 100 },
52965
+ { label: "<$1", min_usd: 0, max_usd: 1 }
52966
+ ];
52967
+ function tallyTiers(rows, totalHolders, stop) {
52968
+ const reachedBottom = stop === "end_of_holders";
52969
+ const smallest = rows.length > 0 ? rows[rows.length - 1].usd : null;
52970
+ const tiers = USD_TIERS.map((tier) => {
52971
+ const members = rows.filter(
52972
+ (row) => row.usd >= tier.min_usd && (tier.max_usd === null || row.usd < tier.max_usd)
52973
+ );
52974
+ const exact = reachedBottom || smallest !== null && smallest < tier.min_usd;
52975
+ const reached = smallest !== null && smallest < (tier.max_usd ?? Infinity);
52976
+ return {
52977
+ ...tier,
52978
+ count: exact || reached ? members.length : null,
52979
+ contracts: exact || reached ? members.filter((r) => r.is_contract).length : null,
52980
+ exact,
52981
+ lower_bound: !exact && reached
52982
+ };
52983
+ });
52984
+ const dust = tiers[tiers.length - 1];
52985
+ if (!dust.exact && stop === "below_smallest_tier" && totalHolders !== null) {
52986
+ const above = rows.filter((row) => row.usd >= 1).length;
52987
+ dust.count = Math.max(totalHolders - above, 0);
52988
+ dust.contracts = null;
52989
+ dust.exact = true;
52990
+ dust.lower_bound = false;
52991
+ }
52992
+ const real = tiers.filter((tier) => tier.min_usd >= 100);
52993
+ return {
52994
+ tiers,
52995
+ holders_100_usd_plus: {
52996
+ count: real.reduce((sum, tier) => sum + (tier.count ?? 0), 0),
52997
+ exact: real.every((tier) => tier.exact)
52998
+ },
52999
+ exhaustive: tiers.every((tier) => tier.exact),
53000
+ smallest_scanned_usd: smallest
53001
+ };
53002
+ }
53003
+ function parseArgs(args) {
53004
+ const rawNetwork = String(args?.network ?? args?.chain ?? "").trim().toLowerCase();
53005
+ const key = CHAIN_ALIASES[rawNetwork] ?? rawNetwork;
53006
+ const chain = CHAINS[key];
53007
+ if (!chain) {
53008
+ throw new SignalsInputError(
53009
+ `network must be one of ${Object.keys(CHAINS).join(", ")} (or a chain id); got ${JSON.stringify(args?.network ?? null)}.`
53010
+ );
53011
+ }
53012
+ const address = String(args?.address ?? "").trim();
53013
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
53014
+ throw new SignalsInputError(
53015
+ `address must be a 0x token contract address; got ${JSON.stringify(args?.address ?? null)}.`
53016
+ );
53017
+ }
53018
+ const priceOverride = args?.price_usd === void 0 || args?.price_usd === null ? null : parseNumberArg(args.price_usd, "price_usd", Number.MIN_VALUE, 1e12, 0);
53019
+ return {
53020
+ network: key,
53021
+ chain,
53022
+ address,
53023
+ maxPages: parseIntegerArg(
53024
+ args?.max_pages,
53025
+ "max_pages",
53026
+ 1,
53027
+ MAX_PAGES,
53028
+ DEFAULT_MAX_PAGES
53029
+ ),
53030
+ priceOverride
53031
+ };
53032
+ }
53033
+ function blockscoutGet(chain, path) {
53034
+ const apiKey = getEnvVar("BLOCKSCOUT_API_KEY");
53035
+ const url = apiKey ? `https://api.blockscout.com/${chain.chainId}/api/v2${path}` : `https://${chain.host}/api/v2${path}`;
53036
+ return fetchJsonWithTimeout(
53037
+ url,
53038
+ `Blockscout (${chain.chainId})`,
53039
+ void 0,
53040
+ apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
53041
+ ).catch((error) => {
53042
+ if (error instanceof SignalsHttpError && error.status === 404) {
53043
+ throw new SignalsInputError(
53044
+ `Blockscout has no token at this address on chain ${chain.chainId}.`
53045
+ );
53046
+ }
53047
+ if (!apiKey && error instanceof SignalsHttpError && (error.status === 403 || error.status === 402)) {
53048
+ throw new Error(
53049
+ `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.`
53050
+ );
53051
+ }
53052
+ throw error;
53053
+ });
53054
+ }
53055
+ var ownCoinGecko = null;
53056
+ async function resolvePrice(chain, address, token) {
53057
+ const blockscoutRate = parseFiniteNumber3(token.exchange_rate);
53058
+ if (blockscoutRate !== null && blockscoutRate > 0) {
53059
+ return { price: blockscoutRate, source: "blockscout_exchange_rate" };
53060
+ }
53061
+ try {
53062
+ if (!ownCoinGecko)
53063
+ ownCoinGecko = new Coingecko2(getCoinGeckoClientOptions());
53064
+ const result2 = await ownCoinGecko.onchain.simple.networks.tokenPrice.getAddresses(
53065
+ address,
53066
+ { network: chain.gecko }
53067
+ );
53068
+ const prices = result2?.data?.attributes?.token_prices ?? {};
53069
+ const price = parseFiniteNumber3(
53070
+ prices[address.toLowerCase()] ?? prices[address]
53071
+ );
53072
+ if (price !== null && price > 0) {
53073
+ return { price, source: "coingecko_onchain_price" };
53074
+ }
53075
+ } catch {
53076
+ }
53077
+ return { price: null, source: null };
53078
+ }
53079
+ async function getTokenHolderDistribution(args, now = Date.now) {
53080
+ const { network, chain, address, maxPages, priceOverride } = parseArgs(args);
53081
+ const started = now();
53082
+ const token = await blockscoutGet(chain, `/tokens/${address}`) ?? {};
53083
+ if (token.type !== void 0 && token.type !== "ERC-20") {
53084
+ throw new SignalsInputError(
53085
+ `this address is a ${String(token.type)} token; holder tiers are computed for ERC-20 tokens only.`
53086
+ );
53087
+ }
53088
+ const decimals = parseFiniteNumber3(token.decimals);
53089
+ if (decimals === null) {
53090
+ throw new Error("Blockscout returned no decimals for this token");
53091
+ }
53092
+ const totalHolders = parseFiniteNumber3(token.holders_count ?? token.holders);
53093
+ const { price, source: priceSource } = priceOverride !== null ? { price: priceOverride, source: "caller" } : await resolvePrice(chain, address, token);
53094
+ const base = {
53095
+ provider: BLOCKSCOUT_PROVIDER,
53096
+ network,
53097
+ chain_id: chain.chainId,
53098
+ address,
53099
+ token: {
53100
+ name: token.name ?? null,
53101
+ symbol: token.symbol ?? null,
53102
+ decimals,
53103
+ total_supply: token.total_supply ?? null
53104
+ },
53105
+ total_holders: totalHolders,
53106
+ price_usd: price,
53107
+ price_source: priceSource
53108
+ };
53109
+ if (price === null) {
53110
+ return {
53111
+ ...base,
53112
+ runtime_status: "degraded",
53113
+ fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
53114
+ tiers: null,
53115
+ 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."
53116
+ };
53117
+ }
53118
+ const rows = [];
53119
+ let pages = 0;
53120
+ let cursor = "";
53121
+ let stop = "end_of_holders";
53122
+ for (; ; ) {
53123
+ if (pages >= maxPages) {
53124
+ stop = "max_pages";
53125
+ break;
53126
+ }
53127
+ if (now() - started > TIME_BUDGET_MS) {
53128
+ stop = "time_budget";
53129
+ break;
53130
+ }
53131
+ const page = await blockscoutGet(
53132
+ chain,
53133
+ `/tokens/${address}/holders${cursor}`
53134
+ ) ?? {};
53135
+ pages += 1;
53136
+ const items = Array.isArray(page.items) ? page.items : [];
53137
+ let belowFloor = false;
53138
+ for (const item of items) {
53139
+ const holder = item ?? {};
53140
+ const units = parseFiniteNumber3(holder.value);
53141
+ if (units === null) continue;
53142
+ const usd2 = units / 10 ** decimals * price;
53143
+ rows.push({ usd: usd2, is_contract: holder.address?.is_contract === true });
53144
+ if (usd2 < 1) {
53145
+ belowFloor = true;
53146
+ break;
53147
+ }
53148
+ }
53149
+ if (belowFloor) {
53150
+ stop = "below_smallest_tier";
53151
+ break;
53152
+ }
53153
+ const next = page.next_page_params;
53154
+ if (!next || typeof next !== "object" || items.length === 0) break;
53155
+ cursor = `?${new URLSearchParams(
53156
+ Object.entries(next).map(([k, v]) => [
53157
+ k,
53158
+ String(v)
53159
+ ])
53160
+ ).toString()}`;
53161
+ }
53162
+ const tally = tallyTiers(rows, totalHolders, stop);
53163
+ return {
53164
+ ...base,
53165
+ runtime_status: "ok",
53166
+ fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
53167
+ holders_scanned: rows.length,
53168
+ pages_fetched: pages,
53169
+ stopped_reason: stop,
53170
+ ...tally,
53171
+ lower_bound: !tally.exhaustive,
53172
+ 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."
53173
+ };
53174
+ }
53175
+ function getAllBlockscoutEndpoints() {
53176
+ return [
53177
+ {
53178
+ metadata: {
53179
+ resource: "blockscout.tokens.holders",
53180
+ operation: "read",
53181
+ tags: ["blockscout", "tokens", "holders", "distribution", "onchain"]
53182
+ },
53183
+ tool: {
53184
+ name: "get_token_holder_distribution",
53185
+ 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(", ")}.
53186
+
53187
+ Args:
53188
+ - network (string, required): ${Object.keys(CHAINS).join(", ")}, or a chain id (1, 8453, 10, 42161, 137).
53189
+ - address (string, required): ERC-20 token contract address.
53190
+ - 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.
53191
+ - price_usd (number, optional): USD price to tier with. Default: Blockscout's exchange rate, then the GeckoTerminal on-chain price.
53192
+
53193
+ Returns:
53194
+ { "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.
53195
+
53196
+ Examples:
53197
+ - { "network": "base", "address": "0x91dA780BC7f4B7Cf19ABE90411a2a296Ec5FF787" }
53198
+ - { "network": "eth", "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "max_pages": 5 }
53199
+
53200
+ Use when:
53201
+ - You need holder counts above a USD size, or how much of the holder count is dust.
53202
+ Don't use when:
53203
+ - You need the largest holders and their share of supply (use get_token_top_holders).`,
53204
+ inputSchema: {
53205
+ type: "object",
53206
+ properties: {
53207
+ network: {
53208
+ type: "string",
53209
+ description: `Chain: ${Object.keys(CHAINS).join(", ")}, or a chain id.`
53210
+ },
53211
+ address: {
53212
+ type: "string",
53213
+ description: "ERC-20 token contract address (0x...)."
53214
+ },
53215
+ max_pages: {
53216
+ type: "integer",
53217
+ minimum: 1,
53218
+ maximum: MAX_PAGES,
53219
+ default: DEFAULT_MAX_PAGES,
53220
+ description: `Holder pages of ${HOLDERS_PAGE_SIZE} to walk (1-${MAX_PAGES}).`
53221
+ },
53222
+ price_usd: {
53223
+ type: "number",
53224
+ exclusiveMinimum: 0,
53225
+ description: "USD price to tier with; default Blockscout's rate, then GeckoTerminal."
53226
+ }
53227
+ },
53228
+ required: ["network", "address"],
53229
+ additionalProperties: false
53230
+ },
53231
+ annotations: VOLATILE_READ_ANNOTATIONS
53232
+ },
53233
+ handler: async (_client, args) => {
53234
+ try {
53235
+ return asTextContentResult(await getTokenHolderDistribution(args));
53236
+ } catch (error) {
53237
+ const message = error instanceof Error ? error.message : String(error);
53238
+ return asTextContentResult(
53239
+ {
53240
+ error: message,
53241
+ runtime_status: error instanceof SignalsInputError ? "invalid_input" : classifyRuntimeStatusFromError(message),
53242
+ tool: "get_token_holder_distribution",
53243
+ provider: BLOCKSCOUT_PROVIDER
53244
+ },
53245
+ true
53246
+ );
53247
+ }
53248
+ }
53249
+ }
53250
+ ];
53251
+ }
53252
+
52297
53253
  // src/freeWriteEndpoints.ts
52298
53254
  var FREE_WRITE_ENDPOINTS = /* @__PURE__ */ new Set([
52299
53255
  // Feedback is a bounded, rate-limited insert into Hive's own table. It
@@ -52863,7 +53819,7 @@ Don't use when:
52863
53819
  }
52864
53820
 
52865
53821
  // src/wrapper/index.ts
52866
- import Coingecko2 from "@coingecko/coingecko-typescript";
53822
+ import Coingecko3 from "@coingecko/coingecko-typescript";
52867
53823
 
52868
53824
  // src/utils/descriptionGenerator.ts
52869
53825
  function needsEnrichment(description) {
@@ -53538,7 +54494,7 @@ var SCHEMA_PATTERNS = [
53538
54494
  TOKEN_DILIGENCE_OUTPUT_SCHEMA
53539
54495
  ],
53540
54496
  [
53541
- /^(alchemy_get_tokens_by_wallet|alchemy_get_token_balances_by_wallet|alchemy_get_native_balance|alchemy_get_asset_transfers|moralis_get_wallet_history|moralis_get_wallet_profitability|moralis_get_wallet_profitability_summary|moralis_get_wallet_pnl|moralis_get_wallet_pnl_summary|moralis_get_wallet_defi_positions|moralis_get_wallet_defi_summary|moralis_get_wallet_insight|helius_get_wallet_balances|helius_get_wallet_history|helius_get_wallet_transfers|get_wallet_balances|get_wallet_stats|get_wallet_chart)$/,
54497
+ /^(get_wallet_balances|get_wallet_pnl|get_wallet_trades|get_wallet_transfers|alchemy_get_tokens_by_wallet|alchemy_get_token_balances_by_wallet|alchemy_get_native_balance|alchemy_get_asset_transfers|moralis_get_wallet_history|moralis_get_wallet_profitability|moralis_get_wallet_profitability_summary|moralis_get_wallet_pnl|moralis_get_wallet_pnl_summary|moralis_get_wallet_defi_positions|moralis_get_wallet_defi_summary|moralis_get_wallet_insight|helius_get_wallet_balances|helius_get_wallet_history|helius_get_wallet_transfers|get_wallet_balances|get_wallet_stats|get_wallet_chart)$/,
53542
54498
  WALLET_INVESTIGATION_OUTPUT_SCHEMA
53543
54499
  ],
53544
54500
  [
@@ -53634,6 +54590,11 @@ var CHECK_TOKEN_SAFETY_OUTPUT_SCHEMA = {
53634
54590
  additionalProperties: true,
53635
54591
  description: "Raw provider risk datapoints; '1' means flagged. A missing key means the provider did not report that signal, not that it passed."
53636
54592
  },
54593
+ summary: {
54594
+ type: "object",
54595
+ additionalProperties: true,
54596
+ 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."
54597
+ },
53637
54598
  holder_count: {},
53638
54599
  token_name: { type: "string" },
53639
54600
  note: { type: "string" }
@@ -54055,11 +55016,11 @@ var TENDERLY_REST_TOOLS = /* @__PURE__ */ new Set([
54055
55016
  "tenderly_estimate_gas",
54056
55017
  "tenderly_decode_calldata"
54057
55018
  ]);
54058
- function matchesProvider(provider, toolName, metadata100) {
55019
+ function matchesProvider(provider, toolName, metadata104) {
54059
55020
  const normalizedProvider = provider.toLowerCase();
54060
55021
  const normalizedToolName = toolName.toLowerCase();
54061
- const resource = metadata100.resource.toLowerCase();
54062
- const tags = metadata100.tags.map((tag) => tag.toLowerCase());
55022
+ const resource = metadata104.resource.toLowerCase();
55023
+ const tags = metadata104.tags.map((tag) => tag.toLowerCase());
54063
55024
  return normalizedToolName.startsWith(`${normalizedProvider}_`) || normalizedToolName.includes(`_${normalizedProvider}_`) || resource.startsWith(normalizedProvider) || resource.includes(`${normalizedProvider}.`) || tags.includes(normalizedProvider);
54064
55025
  }
54065
55026
  var PROVIDER_CATALOG = [
@@ -54070,7 +55031,7 @@ var PROVIDER_CATALOG = [
54070
55031
  type: "MCP discovery and compatibility tools",
54071
55032
  clientStrategy: "hive",
54072
55033
  alwaysAvailable: true,
54073
- matches: (toolName, metadata100) => toolName.startsWith("hive_") || metadata100.resource === "dynamic_tools" || metadata100.tags.includes("hive")
55034
+ matches: (toolName, metadata104) => toolName.startsWith("hive_") || metadata104.resource === "dynamic_tools" || metadata104.tags.includes("hive")
54074
55035
  },
54075
55036
  {
54076
55037
  id: "coingecko",
@@ -54079,7 +55040,7 @@ var PROVIDER_CATALOG = [
54079
55040
  type: "Market data, prices, and metadata",
54080
55041
  clientStrategy: "shared-coingecko",
54081
55042
  alwaysAvailable: true,
54082
- matches: (toolName, metadata100) => matchesProvider("coingecko", toolName, metadata100)
55043
+ matches: (toolName, metadata104) => matchesProvider("coingecko", toolName, metadata104)
54083
55044
  },
54084
55045
  {
54085
55046
  id: "goplus",
@@ -54091,7 +55052,7 @@ var PROVIDER_CATALOG = [
54091
55052
  label: "GOPLUS_APP_KEY or GOPLUS_API_KEY, plus GOPLUS_APP_SECRET",
54092
55053
  isConfigured: () => hasAnyEnvVar("GOPLUS_APP_KEY", "GOPLUS_API_KEY") && hasEnvVar("GOPLUS_APP_SECRET")
54093
55054
  },
54094
- matches: (toolName, metadata100) => matchesProvider("goplus", toolName, metadata100)
55055
+ matches: (toolName, metadata104) => matchesProvider("goplus", toolName, metadata104)
54095
55056
  },
54096
55057
  {
54097
55058
  id: "ccxt",
@@ -54100,7 +55061,7 @@ var PROVIDER_CATALOG = [
54100
55061
  type: "Centralized exchange market data and trading metadata",
54101
55062
  clientStrategy: "shared-ccxt",
54102
55063
  alwaysAvailable: true,
54103
- matches: (toolName, metadata100) => matchesProvider("ccxt", toolName, metadata100)
55064
+ matches: (toolName, metadata104) => matchesProvider("ccxt", toolName, metadata104)
54104
55065
  },
54105
55066
  {
54106
55067
  id: "defillama",
@@ -54109,7 +55070,7 @@ var PROVIDER_CATALOG = [
54109
55070
  type: "TVL, protocol analytics, yields, and bridge data",
54110
55071
  clientStrategy: "internal",
54111
55072
  alwaysAvailable: true,
54112
- matches: (toolName, metadata100) => matchesProvider("defillama", toolName, metadata100)
55073
+ matches: (toolName, metadata104) => matchesProvider("defillama", toolName, metadata104)
54113
55074
  },
54114
55075
  {
54115
55076
  // Listed BEFORE hyperliquid: rwa_get_hyperliquid_dex_perps contains the
@@ -54121,7 +55082,7 @@ var PROVIDER_CATALOG = [
54121
55082
  type: "Tokenized real-world-asset perps: cross-venue funding, markets, and open interest across Hyperliquid HIP-3 builder dexes, Ostium, Avantis, Lighter, and Extended (keyless official APIs)",
54122
55083
  clientStrategy: "internal",
54123
55084
  alwaysAvailable: true,
54124
- matches: (toolName, metadata100) => matchesProvider("rwa", toolName, metadata100)
55085
+ matches: (toolName, metadata104) => matchesProvider("rwa", toolName, metadata104)
54125
55086
  },
54126
55087
  {
54127
55088
  id: "hyperliquid",
@@ -54130,7 +55091,7 @@ var PROVIDER_CATALOG = [
54130
55091
  type: "Hyperliquid perpetual DEX: per-pair volume, funding, open interest, and builder DEXs (tokenized-stock perps)",
54131
55092
  clientStrategy: "internal",
54132
55093
  alwaysAvailable: true,
54133
- matches: (toolName, metadata100) => matchesProvider("hyperliquid", toolName, metadata100)
55094
+ matches: (toolName, metadata104) => matchesProvider("hyperliquid", toolName, metadata104)
54134
55095
  },
54135
55096
  {
54136
55097
  // The Hive-native hive_archive_monitor tool contains the "_archive_"
@@ -54148,7 +55109,7 @@ var PROVIDER_CATALOG = [
54148
55109
  "SHARPE_ARCHIVE_SUPABASE_SERVICE_ROLE_KEY"
54149
55110
  )
54150
55111
  },
54151
- matches: (toolName, metadata100) => matchesProvider("archive", toolName, metadata100)
55112
+ matches: (toolName, metadata104) => matchesProvider("archive", toolName, metadata104)
54152
55113
  },
54153
55114
  {
54154
55115
  id: "polymarket",
@@ -54157,7 +55118,7 @@ var PROVIDER_CATALOG = [
54157
55118
  type: "Polymarket prediction markets: market and event search, market-implied odds, CLOB order books and price history, per-wallet positions and activity (keyless public Gamma, CLOB and Data APIs)",
54158
55119
  clientStrategy: "internal",
54159
55120
  alwaysAvailable: true,
54160
- matches: (toolName, metadata100) => matchesProvider("polymarket", toolName, metadata100)
55121
+ matches: (toolName, metadata104) => matchesProvider("polymarket", toolName, metadata104)
54161
55122
  },
54162
55123
  {
54163
55124
  id: "alternative",
@@ -54166,7 +55127,7 @@ var PROVIDER_CATALOG = [
54166
55127
  type: "Crypto Fear & Greed Index: daily market-wide sentiment composite since 2018 (keyless)",
54167
55128
  clientStrategy: "internal",
54168
55129
  alwaysAvailable: true,
54169
- matches: (_toolName, metadata100) => metadata100.tags.includes("alternative-me")
55130
+ matches: (_toolName, metadata104) => metadata104.tags.includes("alternative-me")
54170
55131
  },
54171
55132
  {
54172
55133
  id: "blockchain",
@@ -54175,7 +55136,16 @@ var PROVIDER_CATALOG = [
54175
55136
  type: "Bitcoin on-chain charts: hash rate, active addresses, transaction count, market price, difficulty, fees, miner revenue (keyless)",
54176
55137
  clientStrategy: "internal",
54177
55138
  alwaysAvailable: true,
54178
- matches: (_toolName, metadata100) => metadata100.tags.includes("blockchain-com")
55139
+ matches: (_toolName, metadata104) => metadata104.tags.includes("blockchain-com")
55140
+ },
55141
+ {
55142
+ id: "blockscout",
55143
+ name: "Blockscout",
55144
+ prefix: "blockscout_",
55145
+ type: "EVM token holder lists and counts from Blockscout explorers (keyless public API; BLOCKSCOUT_API_KEY switches to the PRO API)",
55146
+ clientStrategy: "internal",
55147
+ alwaysAvailable: true,
55148
+ matches: (_toolName, metadata104) => metadata104.tags.includes("blockscout")
54179
55149
  },
54180
55150
  {
54181
55151
  id: "fetch",
@@ -54184,7 +55154,7 @@ var PROVIDER_CATALOG = [
54184
55154
  type: "Allowlisted fetch of long-tail public crypto APIs (the escape hatch when no typed tool covers a source)",
54185
55155
  clientStrategy: "internal",
54186
55156
  alwaysAvailable: true,
54187
- matches: (toolName, metadata100) => matchesProvider("fetch", toolName, metadata100)
55157
+ matches: (toolName, metadata104) => matchesProvider("fetch", toolName, metadata104)
54188
55158
  },
54189
55159
  {
54190
55160
  id: "tenderly",
@@ -54196,7 +55166,7 @@ var PROVIDER_CATALOG = [
54196
55166
  label: "Tenderly REST credentials or TENDERLY_NODE_ACCESS_KEY/TENDERLY_GATEWAY_ACCESS_KEY",
54197
55167
  isConfigured: () => hasTenderlyRestCredentials() || hasTenderlyNodeCredentials()
54198
55168
  },
54199
- matches: (toolName, metadata100) => matchesProvider("tenderly", toolName, metadata100)
55169
+ matches: (toolName, metadata104) => matchesProvider("tenderly", toolName, metadata104)
54200
55170
  },
54201
55171
  {
54202
55172
  id: "moralis",
@@ -54208,7 +55178,7 @@ var PROVIDER_CATALOG = [
54208
55178
  label: "MORALIS_API_KEY",
54209
55179
  isConfigured: () => hasEnvVar("MORALIS_API_KEY")
54210
55180
  },
54211
- matches: (toolName, metadata100) => matchesProvider("moralis", toolName, metadata100)
55181
+ matches: (toolName, metadata104) => matchesProvider("moralis", toolName, metadata104)
54212
55182
  },
54213
55183
  {
54214
55184
  id: "helius",
@@ -54220,7 +55190,7 @@ var PROVIDER_CATALOG = [
54220
55190
  label: "HELIUS_API_KEY",
54221
55191
  isConfigured: () => hasEnvVar("HELIUS_API_KEY")
54222
55192
  },
54223
- matches: (toolName, metadata100) => matchesProvider("helius", toolName, metadata100)
55193
+ matches: (toolName, metadata104) => matchesProvider("helius", toolName, metadata104)
54224
55194
  },
54225
55195
  {
54226
55196
  id: "alchemy",
@@ -54232,11 +55202,11 @@ var PROVIDER_CATALOG = [
54232
55202
  label: "ALCHEMY_API_KEY",
54233
55203
  isConfigured: () => hasEnvVar("ALCHEMY_API_KEY")
54234
55204
  },
54235
- matches: (toolName, metadata100) => matchesProvider("alchemy", toolName, metadata100)
55205
+ matches: (toolName, metadata104) => matchesProvider("alchemy", toolName, metadata104)
54236
55206
  }
54237
55207
  ];
54238
- function getProviderDescriptorForTool(toolName, metadata100) {
54239
- return PROVIDER_CATALOG.find((provider) => provider.matches(toolName, metadata100)) ?? UNKNOWN_PROVIDER_DESCRIPTOR;
55208
+ function getProviderDescriptorForTool(toolName, metadata104) {
55209
+ return PROVIDER_CATALOG.find((provider) => provider.matches(toolName, metadata104)) ?? UNKNOWN_PROVIDER_DESCRIPTOR;
54240
55210
  }
54241
55211
  function getProviderDescriptorForEndpoint(endpoint) {
54242
55212
  return getProviderDescriptorForTool(endpoint.tool.name, endpoint.metadata);
@@ -54265,7 +55235,7 @@ function getProviderStatusCatalog() {
54265
55235
  })
54266
55236
  );
54267
55237
  }
54268
- function getCredentialRequirementForTool(toolName, metadata100) {
55238
+ function getCredentialRequirementForTool(toolName, metadata104) {
54269
55239
  if (toolName === "tenderly_get_supported_networks" || toolName === "tenderly_get_block_number") {
54270
55240
  return null;
54271
55241
  }
@@ -54278,7 +55248,7 @@ function getCredentialRequirementForTool(toolName, metadata100) {
54278
55248
  isConfigured: hasTenderlyNodeCredentials
54279
55249
  };
54280
55250
  }
54281
- return getProviderDescriptorForTool(toolName, metadata100).credentialRequirement ?? null;
55251
+ return getProviderDescriptorForTool(toolName, metadata104).credentialRequirement ?? null;
54282
55252
  }
54283
55253
 
54284
55254
  // src/mcpInvokers.ts
@@ -54309,14 +55279,14 @@ var RESPONSE_FORMAT_SCHEMA = {
54309
55279
  enum: ["json", "markdown"],
54310
55280
  description: "Output format: 'json' for structured data (default) or 'markdown' for human-readable text."
54311
55281
  };
54312
- function normalizeToolSchema(tool96) {
55282
+ function normalizeToolSchema(tool100) {
54313
55283
  const normalized = {
54314
- ...tool96,
54315
- inputSchema: tool96.inputSchema ? JSON.parse(JSON.stringify(tool96.inputSchema)) : tool96.inputSchema,
54316
- title: tool96.title ?? getToolTitle(tool96.name),
55284
+ ...tool100,
55285
+ inputSchema: tool100.inputSchema ? JSON.parse(JSON.stringify(tool100.inputSchema)) : tool100.inputSchema,
55286
+ title: tool100.title ?? getToolTitle(tool100.name),
54317
55287
  annotations: {
54318
- ...getAnnotationsForTool(tool96.name),
54319
- ...tool96.annotations ?? {}
55288
+ ...getAnnotationsForTool(tool100.name),
55289
+ ...tool100.annotations ?? {}
54320
55290
  }
54321
55291
  };
54322
55292
  if (normalized.inputSchema && typeof normalized.inputSchema === "object" && normalized.inputSchema.type === "object") {
@@ -54343,20 +55313,19 @@ function normalizeEndpointSchema(endpoint) {
54343
55313
  var RETIRED_TOOLS_DATE = "2026-09-06";
54344
55314
  var RETIRED_TOOL_REPLACEMENTS = {
54345
55315
  // wallet
54346
- codex_filter_wallets_by_network: "Use alchemy_get_token_balances_by_wallet, alchemy_get_asset_transfers, or moralis_get_wallet_profitability_summary for wallet balances, transfers, and PnL.",
54347
- codex_get_wallet_aggregate_backfill_state: "Use alchemy_get_token_balances_by_wallet, alchemy_get_asset_transfers, or moralis_get_wallet_profitability_summary for wallet balances, transfers, and PnL.",
54348
- codex_get_wallet_label_types: "Use alchemy_get_token_balances_by_wallet, alchemy_get_asset_transfers, or moralis_get_wallet_profitability_summary for wallet balances, transfers, and PnL.",
54349
- filter_network_wallets: "Use alchemy_get_token_balances_by_wallet, alchemy_get_asset_transfers, or moralis_get_wallet_profitability_summary for wallet balances, transfers, and PnL.",
54350
- filter_token_wallets: "Use alchemy_get_token_balances_by_wallet, alchemy_get_asset_transfers, or moralis_get_wallet_profitability_summary for wallet balances, transfers, and PnL.",
54351
- filter_wallets: "Use alchemy_get_token_balances_by_wallet, alchemy_get_asset_transfers, or moralis_get_wallet_profitability_summary for wallet balances, transfers, and PnL.",
54352
- get_wallet_balances: "Use alchemy_get_token_balances_by_wallet, alchemy_get_asset_transfers, or moralis_get_wallet_profitability_summary for wallet balances, transfers, and PnL.",
54353
- get_wallet_chart: "Use alchemy_get_token_balances_by_wallet, alchemy_get_asset_transfers, or moralis_get_wallet_profitability_summary for wallet balances, transfers, and PnL.",
54354
- get_wallet_stats: "Use alchemy_get_token_balances_by_wallet, alchemy_get_asset_transfers, or moralis_get_wallet_profitability_summary for wallet balances, transfers, and PnL.",
54355
- get_wallet_token_events: "Use alchemy_get_token_balances_by_wallet, alchemy_get_asset_transfers, or moralis_get_wallet_profitability_summary for wallet balances, transfers, and PnL.",
55316
+ codex_filter_wallets_by_network: "Use get_wallet_balances, get_wallet_transfers, get_wallet_trades, or get_wallet_pnl for wallet balances, transfers, DEX trades, and PnL.",
55317
+ codex_get_wallet_aggregate_backfill_state: "Use get_wallet_balances, get_wallet_transfers, get_wallet_trades, or get_wallet_pnl for wallet balances, transfers, DEX trades, and PnL.",
55318
+ codex_get_wallet_label_types: "Use get_wallet_balances, get_wallet_transfers, get_wallet_trades, or get_wallet_pnl for wallet balances, transfers, DEX trades, and PnL.",
55319
+ filter_network_wallets: "Use get_wallet_balances, get_wallet_transfers, get_wallet_trades, or get_wallet_pnl for wallet balances, transfers, DEX trades, and PnL.",
55320
+ filter_token_wallets: "Use get_wallet_balances, get_wallet_transfers, get_wallet_trades, or get_wallet_pnl for wallet balances, transfers, DEX trades, and PnL.",
55321
+ filter_wallets: "Use get_wallet_balances, get_wallet_transfers, get_wallet_trades, or get_wallet_pnl for wallet balances, transfers, DEX trades, and PnL.",
55322
+ get_wallet_chart: "Use get_wallet_balances, get_wallet_transfers, get_wallet_trades, or get_wallet_pnl for wallet balances, transfers, DEX trades, and PnL.",
55323
+ get_wallet_stats: "Use get_wallet_balances, get_wallet_transfers, get_wallet_trades, or get_wallet_pnl for wallet balances, transfers, DEX trades, and PnL.",
55324
+ get_wallet_token_events: "Use get_wallet_balances, get_wallet_transfers, get_wallet_trades, or get_wallet_pnl for wallet balances, transfers, DEX trades, and PnL.",
54356
55325
  // holders
54357
- get_token_holders: "Use get_token_top_holders for holders on any network, coingecko_get_onchain_token_top_traders for traders where CoinGecko serves them (base and similar; not Ethereum on the current plan), or moralis_get_token_holder_metrics for holder metrics.",
54358
- get_token_top_traders: "Use get_token_top_holders for holders on any network, coingecko_get_onchain_token_top_traders for traders where CoinGecko serves them (base and similar; not Ethereum on the current plan), or moralis_get_token_holder_metrics for holder metrics.",
54359
- get_top_holders_percentage: "Use get_token_top_holders for holders on any network, coingecko_get_onchain_token_top_traders for traders where CoinGecko serves them (base and similar; not Ethereum on the current plan), or moralis_get_token_holder_metrics for holder metrics.",
55326
+ get_token_holders: "Use get_token_top_holders for holders on any network, or coingecko_get_onchain_token_top_traders for traders (tokens created after 2023-09-01; not wrapped native tokens or stablecoins).",
55327
+ get_token_top_traders: "Use get_token_top_holders for holders on any network, or coingecko_get_onchain_token_top_traders for traders (tokens created after 2023-09-01; not wrapped native tokens or stablecoins).",
55328
+ get_top_holders_percentage: "Use get_token_top_holders for holders on any network, or coingecko_get_onchain_token_top_traders for traders (tokens created after 2023-09-01; not wrapped native tokens or stablecoins).",
54360
55329
  // pairs
54361
55330
  codex_get_detailed_token_stats: "Use get_token_pools, get_pool_info, get_pools_by_address, moralis_get_pair_stats, or get_new_pools for pool and pair data.",
54362
55331
  codex_get_windowed_pair_stats: "Use get_token_pools, get_pool_info, get_pools_by_address, moralis_get_pair_stats, or get_new_pools for pool and pair data.",
@@ -54414,7 +55383,12 @@ var HELIUS_WALLET_PLAN = "because Helius answers 403 for it on Hive's current He
54414
55383
  var DEFILLAMA_PRO = "because it needs a DeFiLlama Pro plan, which Hive does not have";
54415
55384
  var DEFILLAMA_DERIVATIVES_REPLACEMENT = "Use defillama_get_open_interest, defillama_get_derivatives_aggregator_volumes, or get_derivatives_exchanges for derivatives activity.";
54416
55385
  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";
54417
- 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.";
55386
+ var ALCHEMY_BUNDLE_REPLACEMENT = "Use tenderly_simulate_bundle or tenderly_node_simulate_bundle for multi-transaction simulation.";
55387
+ var ALCHEMY_SIMULATION_SUNSET = 'because Alchemy shut down its Transaction Simulation API on 2026-09-30 and now answers "Unsupported method" on every plan';
55388
+ 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.";
55389
+ var MORALIS_PAUSED = "because Hive's Moralis account is paused and will not be renewed";
55390
+ var MORALIS_BALANCES_REPLACEMENT = "Use get_wallet_balances for priced EVM balances across chains.";
55391
+ var MORALIS_PNL_REPLACEMENT = "Use get_wallet_pnl for realized and unrealized PnL per wallet and per token.";
54418
55392
  var CCXT_PRIVATE = "because every exchange that serves it requires an authenticated exchange account";
54419
55393
  var CCXT_BORROW_REPLACEMENT = "Use ccxt_fetch_borrow_rate_history or ccxt_fetch_borrow_rate_histories (public) and read the latest point.";
54420
55394
  var UNSERVABLE_TOOLS = {
@@ -54434,10 +55408,6 @@ var UNSERVABLE_TOOLS = {
54434
55408
  reason: COINGECKO_ENTERPRISE,
54435
55409
  replacement: COINGECKO_SUPPLY_REPLACEMENT
54436
55410
  },
54437
- get_coin_insights: {
54438
- reason: COINGECKO_ENTERPRISE,
54439
- replacement: "No direct replacement; use get_coins_market_data and get_coin_tickers for the market facts behind an insight."
54440
- },
54441
55411
  helius_get_wallet_identity: {
54442
55412
  reason: HELIUS_WALLET_PLAN,
54443
55413
  replacement: "No other wallet-identity source is live on Hive's plans; use helius_get_wallet_history or helius_get_transfers_by_address for the wallet's activity."
@@ -54498,9 +55468,74 @@ var UNSERVABLE_TOOLS = {
54498
55468
  reason: CCXT_PRIVATE,
54499
55469
  replacement: CCXT_BORROW_REPLACEMENT
54500
55470
  },
55471
+ alchemy_simulate_asset_changes: {
55472
+ reason: ALCHEMY_SIMULATION_SUNSET,
55473
+ replacement: ALCHEMY_SIMULATION_REPLACEMENT,
55474
+ date: "2026-10-09"
55475
+ },
55476
+ alchemy_simulate_execution: {
55477
+ reason: ALCHEMY_SIMULATION_SUNSET,
55478
+ replacement: ALCHEMY_SIMULATION_REPLACEMENT,
55479
+ date: "2026-10-09"
55480
+ },
55481
+ alchemy_is_holder_of_contract: {
55482
+ reason: 'because Alchemy deprecated the isHolderOfContract endpoint, which now answers 404 "This API endpoint has been deprecated"',
55483
+ 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.",
55484
+ date: "2026-10-09"
55485
+ },
54501
55486
  goplus_simulate_solana_transaction: {
54502
55487
  reason: 'because GoPlus answers "system error" to every Solana pre-execution request, including a fresh valid transfer',
54503
55488
  replacement: "Use get_solana_token_security for the tokens the transaction touches; Hive has no other Solana pre-signing simulation."
55489
+ },
55490
+ moralis_get_wallet_token_balances: {
55491
+ reason: MORALIS_PAUSED,
55492
+ replacement: MORALIS_BALANCES_REPLACEMENT,
55493
+ date: "2026-10-14"
55494
+ },
55495
+ moralis_get_wallet_token_balances_price: {
55496
+ reason: MORALIS_PAUSED,
55497
+ replacement: MORALIS_BALANCES_REPLACEMENT,
55498
+ date: "2026-10-14"
55499
+ },
55500
+ moralis_get_wallet_net_worth: {
55501
+ reason: MORALIS_PAUSED,
55502
+ replacement: MORALIS_BALANCES_REPLACEMENT,
55503
+ date: "2026-10-14"
55504
+ },
55505
+ moralis_get_wallet_profitability: {
55506
+ reason: MORALIS_PAUSED,
55507
+ replacement: MORALIS_PNL_REPLACEMENT,
55508
+ date: "2026-10-14"
55509
+ },
55510
+ moralis_get_wallet_profitability_summary: {
55511
+ reason: MORALIS_PAUSED,
55512
+ replacement: MORALIS_PNL_REPLACEMENT,
55513
+ date: "2026-10-14"
55514
+ },
55515
+ moralis_get_wallet_pnl: {
55516
+ reason: MORALIS_PAUSED,
55517
+ replacement: MORALIS_PNL_REPLACEMENT,
55518
+ date: "2026-10-14"
55519
+ },
55520
+ moralis_get_wallet_pnl_summary: {
55521
+ reason: MORALIS_PAUSED,
55522
+ replacement: MORALIS_PNL_REPLACEMENT,
55523
+ date: "2026-10-14"
55524
+ },
55525
+ moralis_get_wallet_swaps: {
55526
+ reason: MORALIS_PAUSED,
55527
+ replacement: "Use get_wallet_trades for a wallet's DEX buys and sells.",
55528
+ date: "2026-10-14"
55529
+ },
55530
+ moralis_get_wallet_token_transfers: {
55531
+ reason: MORALIS_PAUSED,
55532
+ replacement: "Use get_wallet_transfers for EVM token transfers, or helius_get_wallet_transfers on Solana.",
55533
+ date: "2026-10-14"
55534
+ },
55535
+ moralis_get_token_top_traders: {
55536
+ reason: MORALIS_PAUSED,
55537
+ replacement: "Use coingecko_get_onchain_token_top_traders for a token's most profitable traders.",
55538
+ date: "2026-10-14"
54504
55539
  }
54505
55540
  };
54506
55541
  function isUnservableTool(toolName) {
@@ -54510,7 +55545,7 @@ function getRetiredTool(toolName) {
54510
55545
  if (isUnservableTool(toolName)) {
54511
55546
  const entry2 = UNSERVABLE_TOOLS[toolName];
54512
55547
  return {
54513
- date: UNSERVABLE_TOOLS_DATE,
55548
+ date: entry2.date ?? UNSERVABLE_TOOLS_DATE,
54514
55549
  cause: entry2.reason,
54515
55550
  replacement: entry2.replacement
54516
55551
  };
@@ -54687,7 +55722,40 @@ function boundedJsonTruncationStub(originalLength, note, characterLimit) {
54687
55722
  ];
54688
55723
  return candidates.find((candidate) => candidate.length <= characterLimit) ?? "";
54689
55724
  }
54690
- function smartTruncate(text, characterLimit = CHARACTER_LIMIT) {
55725
+ function inputSchemaParamNames(schema) {
55726
+ const properties = schema?.properties;
55727
+ return properties && typeof properties === "object" ? Object.keys(properties) : [];
55728
+ }
55729
+ function truncationRecallHint(params = [], nextOffset) {
55730
+ const names = new Set(params);
55731
+ if (names.has("offset")) {
55732
+ return `Re-call with offset=${nextOffset ?? "_pagination.next_offset"} to continue, or add filters.`;
55733
+ }
55734
+ const sizeParam = ["limit", "per_page", "page_size", "count"].find(
55735
+ (name) => names.has(name)
55736
+ );
55737
+ 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.";
55738
+ }
55739
+ function truncationPagination(kept, original, context) {
55740
+ const params = [...context.params ?? []];
55741
+ const head = `Response truncated from ${original} to ${kept} items to fit the response budget.`;
55742
+ const base = {
55743
+ count: kept,
55744
+ total_count: original,
55745
+ has_more: kept < original
55746
+ };
55747
+ if (!params.includes("offset")) {
55748
+ return { ...base, note: `${head} ${truncationRecallHint(params)}` };
55749
+ }
55750
+ const start = typeof context.offset === "number" && Number.isFinite(context.offset) ? Math.max(0, Math.floor(context.offset)) : 0;
55751
+ return {
55752
+ ...base,
55753
+ offset: start,
55754
+ next_offset: start + kept,
55755
+ note: `${head} ${truncationRecallHint(params, start + kept)}`
55756
+ };
55757
+ }
55758
+ function smartTruncate(text, characterLimit = CHARACTER_LIMIT, pagination = {}) {
54691
55759
  if (text.length <= characterLimit) return text;
54692
55760
  const originalLength = text.length;
54693
55761
  const head = text.trimStart();
@@ -54748,14 +55816,11 @@ function smartTruncate(text, characterLimit = CHARACTER_LIMIT) {
54748
55816
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
54749
55817
  const obj = parsed;
54750
55818
  if (pagInfo && pagInfo.kept < pagInfo.original) {
54751
- obj._pagination = {
54752
- count: pagInfo.kept,
54753
- total_count: pagInfo.original,
54754
- offset: 0,
54755
- has_more: pagInfo.kept < pagInfo.original,
54756
- next_offset: pagInfo.kept,
54757
- note: `Response truncated from ${pagInfo.original} to ${pagInfo.kept} items. Re-call with offset=${pagInfo.kept} or add filters to see more.`
54758
- };
55819
+ obj._pagination = truncationPagination(
55820
+ pagInfo.kept,
55821
+ pagInfo.original,
55822
+ pagination
55823
+ );
54759
55824
  }
54760
55825
  if (dropped.length > 0) {
54761
55826
  obj._dropped_fields = dropped;
@@ -54826,9 +55891,9 @@ function withExecMarker(result2, marker) {
54826
55891
  function humanizePropertyName(propName) {
54827
55892
  return propName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim().toLowerCase();
54828
55893
  }
54829
- function fillMissingInputDescriptions(tool96) {
54830
- const schema = tool96.inputSchema;
54831
- if (!schema?.properties) return tool96;
55894
+ function fillMissingInputDescriptions(tool100) {
55895
+ const schema = tool100.inputSchema;
55896
+ if (!schema?.properties) return tool100;
54832
55897
  let changed = false;
54833
55898
  const properties = {};
54834
55899
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
@@ -54839,14 +55904,14 @@ function fillMissingInputDescriptions(tool96) {
54839
55904
  changed = true;
54840
55905
  properties[propName] = {
54841
55906
  ...propSchema,
54842
- description: `${humanizePropertyName(propName)} parameter for ${tool96.name}. See the tool Args section for accepted values and examples.`
55907
+ description: `${humanizePropertyName(propName)} parameter for ${tool100.name}. See the tool Args section for accepted values and examples.`
54843
55908
  };
54844
55909
  }
54845
- if (!changed) return tool96;
55910
+ if (!changed) return tool100;
54846
55911
  return {
54847
- ...tool96,
55912
+ ...tool100,
54848
55913
  inputSchema: {
54849
- ...tool96.inputSchema,
55914
+ ...tool100.inputSchema,
54850
55915
  properties
54851
55916
  }
54852
55917
  };
@@ -54872,24 +55937,25 @@ function stripProviderPrefix(toolName) {
54872
55937
  var fallbackCoinGeckoClient = null;
54873
55938
  function getFallbackCoinGeckoClient() {
54874
55939
  if (!fallbackCoinGeckoClient) {
54875
- fallbackCoinGeckoClient = new Coingecko2(getCoinGeckoClientOptions());
55940
+ fallbackCoinGeckoClient = new Coingecko3(getCoinGeckoClientOptions());
54876
55941
  }
54877
55942
  return fallbackCoinGeckoClient;
54878
55943
  }
54879
55944
  function isKeylessCoinGeckoClient(client2) {
54880
- return client2 instanceof Coingecko2 && !client2.proAPIKey && !client2.demoAPIKey;
55945
+ return client2 instanceof Coingecko3 && !client2.proAPIKey && !client2.demoAPIKey;
54881
55946
  }
54882
- function createSafeHandler(handler96, metadata100, toolName) {
55947
+ function createSafeHandler(handler100, metadata104, toolName, inputSchema) {
55948
+ const schemaParams = inputSchemaParamNames(inputSchema);
54883
55949
  const credentialRequirement = getCredentialRequirementForTool(
54884
55950
  toolName,
54885
- metadata100
55951
+ metadata104
54886
55952
  );
54887
55953
  return async (client2, args) => {
54888
55954
  const t0 = Date.now();
54889
55955
  try {
54890
- let usedFallback = metadata100.tags.includes("coingecko") && isKeylessCoinGeckoClient(client2);
55956
+ let usedFallback = metadata104.tags.includes("coingecko") && isKeylessCoinGeckoClient(client2);
54891
55957
  let effectiveClient = client2;
54892
- if (effectiveClient == null && metadata100.tags.includes("coingecko")) {
55958
+ if (effectiveClient == null && metadata104.tags.includes("coingecko")) {
54893
55959
  effectiveClient = getFallbackCoinGeckoClient();
54894
55960
  usedFallback = isKeylessCoinGeckoClient(effectiveClient);
54895
55961
  }
@@ -54920,7 +55986,7 @@ function createSafeHandler(handler96, metadata100, toolName) {
54920
55986
  );
54921
55987
  }
54922
55988
  }
54923
- let result2 = await handler96(effectiveClient, safeArgs);
55989
+ let result2 = await handler100(effectiveClient, safeArgs);
54924
55990
  const observedAt = (/* @__PURE__ */ new Date()).toISOString();
54925
55991
  if (result2._hiveExec?.source === "fallback") usedFallback = true;
54926
55992
  if (!result2.isError && credentialRequirement && !credentialRequirement.isConfigured() && result2?.content?.[0]?.type === "text" && result2.content[0].text) {
@@ -54975,7 +56041,15 @@ function createSafeHandler(handler96, metadata100, toolName) {
54975
56041
  withExecMarker(
54976
56042
  {
54977
56043
  ...result2,
54978
- content: [{ type: "text", text: smartTruncate(text) }]
56044
+ content: [
56045
+ {
56046
+ type: "text",
56047
+ text: smartTruncate(text, CHARACTER_LIMIT, {
56048
+ params: schemaParams,
56049
+ offset: safeArgs.offset
56050
+ })
56051
+ }
56052
+ ]
54979
56053
  },
54980
56054
  {
54981
56055
  truncated: true,
@@ -55138,14 +56212,15 @@ function getAllWrappedEndpoints() {
55138
56212
  ...getAllArchiveEndpoints(),
55139
56213
  ...getAllPolymarketEndpoints(),
55140
56214
  ...getAllSignalsEndpoints(),
56215
+ ...getAllBlockscoutEndpoints(),
55141
56216
  ...getAllFetchEndpoints()
55142
56217
  ];
55143
56218
  const allEndpointsJSON = wrapper;
55144
56219
  const updatedTools = [];
55145
56220
  for (const endpoint of allTool) {
55146
- let { tool: tool96 } = endpoint;
56221
+ let { tool: tool100 } = endpoint;
55147
56222
  let matchedItem = null;
55148
- const originalName = tool96.name;
56223
+ const originalName = tool100.name;
55149
56224
  const strippedName = stripProviderPrefix(originalName);
55150
56225
  for (const endpoints of Object.values(allEndpointsJSON)) {
55151
56226
  for (const item of endpoints) {
@@ -55171,10 +56246,10 @@ function getAllWrappedEndpoints() {
55171
56246
  }
55172
56247
  if (matchedItem) {
55173
56248
  let toolInstanse = {
55174
- ...tool96,
56249
+ ...tool100,
55175
56250
  name: matchedItem.tool.name,
55176
- title: tool96.title ?? getToolTitle(matchedItem.tool.name),
55177
- description: matchedItem.tool.description ?? tool96.description
56251
+ title: tool100.title ?? getToolTitle(matchedItem.tool.name),
56252
+ description: matchedItem.tool.description ?? tool100.description
55178
56253
  };
55179
56254
  if (!toolInstanse.annotations) {
55180
56255
  toolInstanse.annotations = getAnnotationsForTool(toolInstanse.name);
@@ -55193,7 +56268,8 @@ function getAllWrappedEndpoints() {
55193
56268
  handler: createSafeHandler(
55194
56269
  endpoint.handler,
55195
56270
  endpoint.metadata,
55196
- toolInstanse.name
56271
+ toolInstanse.name,
56272
+ toolInstanse.inputSchema
55197
56273
  ),
55198
56274
  metadata: endpoint.metadata,
55199
56275
  tool: toolInstanse
@@ -55216,10 +56292,10 @@ function getAllWrappedEndpoints() {
55216
56292
  "signals_",
55217
56293
  "fetch_"
55218
56294
  ];
55219
- if (selfDescribedPrefixes.some((p) => tool96.name.startsWith(p)) || isFreeWriteEndpoint(tool96.name)) {
56295
+ if (selfDescribedPrefixes.some((p) => tool100.name.startsWith(p)) || isFreeWriteEndpoint(tool100.name)) {
55220
56296
  let toolInstanse = {
55221
- ...tool96,
55222
- title: tool96.title ?? getToolTitle(tool96.name)
56297
+ ...tool100,
56298
+ title: tool100.title ?? getToolTitle(tool100.name)
55223
56299
  };
55224
56300
  if (!toolInstanse.annotations) {
55225
56301
  toolInstanse.annotations = getAnnotationsForTool(toolInstanse.name);
@@ -55237,14 +56313,15 @@ function getAllWrappedEndpoints() {
55237
56313
  handler: createSafeHandler(
55238
56314
  endpoint.handler,
55239
56315
  endpoint.metadata,
55240
- toolInstanse.name
56316
+ toolInstanse.name,
56317
+ toolInstanse.inputSchema
55241
56318
  ),
55242
56319
  metadata: endpoint.metadata,
55243
56320
  tool: toolInstanse
55244
56321
  });
55245
56322
  } else {
55246
56323
  secureLogger.debug(
55247
- `No match found in allEndpoints for tool: ${tool96.name}`
56324
+ `No match found in allEndpoints for tool: ${tool100.name}`
55248
56325
  );
55249
56326
  }
55250
56327
  }
@@ -55260,9 +56337,9 @@ function getToolByCategory(category) {
55260
56337
  const allWrappedTools = getAllWrappedEndpoints();
55261
56338
  const tools5 = [];
55262
56339
  for (const toolName of toolNameList) {
55263
- const tool96 = allWrappedTools.find((tool97) => tool97.tool.name === toolName);
55264
- if (tool96) {
55265
- tools5.push(tool96);
56340
+ const tool100 = allWrappedTools.find((tool101) => tool101.tool.name === toolName);
56341
+ if (tool100) {
56342
+ tools5.push(tool100);
55266
56343
  }
55267
56344
  }
55268
56345
  return tools5;
@@ -55485,8 +56562,13 @@ var ToolRegistry = [
55485
56562
  {
55486
56563
  category: "Portfolio & Wallet",
55487
56564
  name: "get_portfolio_wallet_endpoints",
55488
- description: "Endpoints for tracking user wallet balances, transaction history, portfolio positions across protocols and chains, net asset calculations, token holdings analysis, comprehensive wallet activity monitoring, real-time balance tracking, balance update history over time, multi-token portfolio analysis, balance changes with transaction context, financial auditing capabilities for individual users and addresses, wallet filtering and discovery, NFT holdings tracking, wallet PnL, DeFi positions, swap history, and wallet insight metrics. Provider guide: Use alchemy_ tools for default EVM portfolio, balances, transfers, and NFT holdings. Use moralis_ tools only for unique wallet intelligence (PnL, DeFi positions, decoded wallet insight/history). Use helius_ tools for Solana-native wallet depth (native SOL balance, SPL token accounts, Digital Asset Standard assets owned by a wallet).",
56565
+ description: "Endpoints for tracking user wallet balances, transaction history, portfolio positions across protocols and chains, net asset calculations, token holdings analysis, comprehensive wallet activity monitoring, real-time balance tracking, balance update history over time, multi-token portfolio analysis, balance changes with transaction context, financial auditing capabilities for individual users and addresses, wallet filtering and discovery, NFT holdings tracking, wallet PnL, DeFi positions, swap history, and wallet insight metrics. Provider guide: Use get_wallet_balances, get_wallet_pnl, get_wallet_trades, and get_wallet_transfers (CoinGecko) for EVM balances, PnL, DEX trades, and transfers. Use alchemy_ tools for NFT holdings, raw transfers, and EVM chains CoinGecko does not cover. Use moralis_ tools only for DeFi positions and decoded wallet insight/history. Use helius_ tools for Solana-native wallet depth (native SOL balance, SPL token accounts, Digital Asset Standard assets owned by a wallet).",
55489
56566
  tools: [
56567
+ // CoinGecko wallet data (default for EVM balances, PnL, DEX trades, transfers)
56568
+ "get_wallet_balances",
56569
+ "get_wallet_pnl",
56570
+ "get_wallet_trades",
56571
+ "get_wallet_transfers",
55490
56572
  "alchemy_get_tokens_by_wallet",
55491
56573
  "alchemy_get_token_balances_by_wallet",
55492
56574
  "alchemy_get_nfts_by_wallet",
@@ -55517,24 +56599,15 @@ var ToolRegistry = [
55517
56599
  "alchemy_solana_get_transaction_with_compression_info",
55518
56600
  // Moralis wallet intelligence tools
55519
56601
  "moralis_get_wallet_history",
55520
- "moralis_get_wallet_profitability",
55521
- "moralis_get_wallet_profitability_summary",
55522
- "moralis_get_wallet_pnl",
55523
- "moralis_get_wallet_pnl_summary",
55524
56602
  "moralis_get_wallet_defi_positions",
55525
56603
  "moralis_get_wallet_defi_summary",
55526
- "moralis_get_wallet_swaps",
55527
56604
  "moralis_get_wallet_chain_activity",
55528
56605
  "moralis_get_wallet_stats",
55529
56606
  "moralis_get_wallet_insight",
55530
- "moralis_get_wallet_token_balances",
55531
- "moralis_get_wallet_token_balances_price",
55532
- "moralis_get_wallet_net_worth",
55533
56607
  "moralis_get_wallet_approvals",
55534
56608
  "moralis_get_wallet_active_chains",
55535
56609
  "moralis_get_native_balance",
55536
56610
  "moralis_get_native_balances_batch",
55537
- "moralis_get_wallet_token_transfers",
55538
56611
  "moralis_get_sol_balance",
55539
56612
  "moralis_get_sol_token_balances",
55540
56613
  "moralis_get_sol_portfolio",
@@ -55594,6 +56667,7 @@ var ToolRegistry = [
55594
56667
  "get_tokens_by_address",
55595
56668
  "get_token_info",
55596
56669
  "get_token_top_holders",
56670
+ "get_token_holder_distribution",
55597
56671
  "get_token_holders_chart",
55598
56672
  "get_multi_tokens",
55599
56673
  "get_recently_updated_tokens",
@@ -55618,7 +56692,6 @@ var ToolRegistry = [
55618
56692
  "moralis_get_token_metadata_batch",
55619
56693
  "moralis_get_token_holders",
55620
56694
  "moralis_get_token_transfers",
55621
- "moralis_get_token_top_traders",
55622
56695
  "moralis_get_token_details",
55623
56696
  "moralis_get_sol_token_price",
55624
56697
  "moralis_get_sol_token_metadata",
@@ -55696,7 +56769,6 @@ var ToolRegistry = [
55696
56769
  "alchemy_get_floor_price",
55697
56770
  "alchemy_get_contracts_for_owner",
55698
56771
  "alchemy_is_spam_contract",
55699
- "alchemy_is_holder_of_contract",
55700
56772
  "alchemy_solana_get_asset",
55701
56773
  "alchemy_solana_get_asset_proof",
55702
56774
  "alchemy_solana_get_asset_proofs",
@@ -55798,9 +56870,7 @@ var ToolRegistry = [
55798
56870
  "tenderly_get_contract_abi",
55799
56871
  "tenderly_get_storage_changes",
55800
56872
  "tenderly_get_transactions_range",
55801
- "alchemy_get_token_allowance",
55802
- "alchemy_simulate_asset_changes",
55803
- "alchemy_simulate_execution"
56873
+ "alchemy_get_token_allowance"
55804
56874
  ]
55805
56875
  },
55806
56876
  {
@@ -55858,6 +56928,7 @@ var ToolRegistry = [
55858
56928
  "get_exchanges_list",
55859
56929
  "get_derivatives_exchanges_list",
55860
56930
  "get_news",
56931
+ "get_coin_insights",
55861
56932
  "moralis_search_entities",
55862
56933
  "moralis_get_entity",
55863
56934
  "moralis_get_entities_by_category",
@@ -55973,7 +57044,7 @@ import {
55973
57044
  import { existsSync, readFileSync, readdirSync } from "fs";
55974
57045
  import { dirname, resolve as resolvePath } from "path";
55975
57046
  import { fileURLToPath } from "url";
55976
- import Coingecko4 from "@coingecko/coingecko-typescript";
57047
+ import Coingecko5 from "@coingecko/coingecko-typescript";
55977
57048
 
55978
57049
  // src/tools/dynamic-tools.ts
55979
57050
  import { z as z4 } from "zod";
@@ -56289,20 +57360,51 @@ function isUnmarkedErrorResponse(result2) {
56289
57360
  }
56290
57361
  return false;
56291
57362
  }
57363
+ var PROVIDER_SIDE_ERROR_COPY = {
57364
+ plan_required: {
57365
+ 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.",
57366
+ suggestion: "Use search_tools to pick another tool for the same data. Retrying unchanged will fail again."
57367
+ },
57368
+ rate_limited: {
57369
+ message: "The upstream provider rate limit was hit (runtime_status rate_limited).",
57370
+ suggestion: "Wait about 60 seconds before one retry, or narrow the request with this tool's filters."
57371
+ },
57372
+ missing_key: {
57373
+ 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."
57374
+ }
57375
+ };
57376
+ var VENDOR_ACCOUNT_URL = /https?:\/\/[^\s"')\]]*(?:billing|pricing|plan|upgrade|admin|dashboard|account|subscri|checkout)[^\s"')\]]*/gi;
57377
+ function sanitizeProviderError(message, suggestion) {
57378
+ const copy = PROVIDER_SIDE_ERROR_COPY[classifyRuntimeStatusFromError(message)];
57379
+ return {
57380
+ message: (copy?.message ?? message).replace(
57381
+ VENDOR_ACCOUNT_URL,
57382
+ "[link removed]"
57383
+ ),
57384
+ suggestion: copy?.suggestion ?? suggestion,
57385
+ providerSide: Boolean(copy)
57386
+ };
57387
+ }
57388
+ function collapseErrorMarkers(text) {
57389
+ return text.replace(/(?:\*\*Error:\*\*\s*){2,}/g, "**Error:** ");
57390
+ }
56292
57391
  function normalizeErrorText(text, toolName, args) {
56293
57392
  let errorMsg = "";
56294
57393
  let code = "";
56295
57394
  let suggestion = "";
56296
- let alternatives = "";
57395
+ let alternatives = [];
56297
57396
  let documentation = "";
56298
57397
  try {
56299
57398
  const parsed = JSON.parse(text);
56300
57399
  if (parsed && typeof parsed === "object" && typeof parsed.error === "string") {
56301
57400
  errorMsg = parsed.error;
57401
+ if (typeof parsed.message === "string" && parsed.message.trim() && !errorMsg.includes(parsed.message)) {
57402
+ errorMsg += `: ${parsed.message.replace(/https?:\/\/\S+/g, "[url]")}`;
57403
+ }
56302
57404
  code = typeof parsed.code === "string" ? parsed.code : "";
56303
57405
  suggestion = parsed.suggestion || "";
56304
57406
  if (Array.isArray(parsed.alternatives) && parsed.alternatives.length > 0) {
56305
- alternatives = parsed.alternatives.join(", ");
57407
+ alternatives = parsed.alternatives.map(String);
56306
57408
  }
56307
57409
  documentation = parsed.documentation || "";
56308
57410
  }
@@ -56310,7 +57412,18 @@ function normalizeErrorText(text, toolName, args) {
56310
57412
  }
56311
57413
  if (errorMsg) {
56312
57414
  const bareErrorMsg = errorMsg.replace(/^(?:\s*\*\*Error:\*\*\s*)+/, "");
56313
- let normalized = `**Error:** ${bareErrorMsg}`;
57415
+ if (bareErrorMsg.includes("\n\n**Code:**")) {
57416
+ return collapseErrorMarkers(`**Error:** ${bareErrorMsg}`);
57417
+ }
57418
+ const safe = sanitizeProviderError(bareErrorMsg, suggestion);
57419
+ suggestion = safe.suggestion;
57420
+ const prefix = toolName?.split("_")[0];
57421
+ if (safe.providerSide && prefix) {
57422
+ alternatives = alternatives.filter(
57423
+ (name) => !name.startsWith(`${prefix}_`)
57424
+ );
57425
+ }
57426
+ let normalized = `**Error:** ${safe.message}`;
56314
57427
  normalized += `
56315
57428
 
56316
57429
  **Code:** ${code || "API_ERROR"}`;
@@ -56326,15 +57439,29 @@ function normalizeErrorText(text, toolName, args) {
56326
57439
 
56327
57440
  **Params:** ${truncated}`;
56328
57441
  }
56329
- if (alternatives) normalized += `
57442
+ if (alternatives.length > 0) {
57443
+ normalized += `
56330
57444
 
56331
- **Alternatives:** ${alternatives}`;
57445
+ **Alternatives:** ${alternatives.join(", ")}`;
57446
+ }
56332
57447
  if (documentation) normalized += `
56333
57448
 
56334
57449
  **Documentation:** ${documentation}`;
56335
57450
  return normalized;
56336
57451
  }
56337
- let result2 = text;
57452
+ let result2 = collapseErrorMarkers(text);
57453
+ const head = /^\*\*Error:\*\* (.*?)(?=\n\n\*\*|$)/s.exec(result2);
57454
+ if (head) {
57455
+ const safe = sanitizeProviderError(head[1], "");
57456
+ result2 = result2.replace(head[0], `**Error:** ${safe.message}`);
57457
+ if (safe.suggestion) {
57458
+ result2 = result2.replace(
57459
+ /\*\*Suggestion:\*\* [^\n]*/,
57460
+ `**Suggestion:** ${safe.suggestion}`
57461
+ );
57462
+ }
57463
+ }
57464
+ result2 = result2.replace(VENDOR_ACCOUNT_URL, "[link removed]");
56338
57465
  if (toolName && !result2.includes("**Tool:**")) {
56339
57466
  result2 += `
56340
57467
 
@@ -56379,8 +57506,8 @@ function normalizeToolResponse(result2, toolName, args) {
56379
57506
  }
56380
57507
 
56381
57508
  // src/utils/clientRouter.ts
56382
- function getClientForTool(metadata100, clients) {
56383
- const provider = getProviderDescriptorForTool("", metadata100);
57509
+ function getClientForTool(metadata104, clients) {
57510
+ const provider = getProviderDescriptorForTool("", metadata104);
56384
57511
  if (provider.clientStrategy === "shared-goplus") {
56385
57512
  return clients.goplusClient ?? void 0;
56386
57513
  }
@@ -57019,7 +58146,7 @@ function describeErrorCode(code) {
57019
58146
  // src/utils/agentErrorCopy.ts
57020
58147
  function agentErrorCopy(status, context = {}) {
57021
58148
  const provider = context.provider || "the provider";
57022
- const tool96 = context.tool || "this tool";
58149
+ const tool100 = context.tool || "this tool";
57023
58150
  switch (status) {
57024
58151
  case "ok":
57025
58152
  return {
@@ -57029,17 +58156,17 @@ function agentErrorCopy(status, context = {}) {
57029
58156
  case "invalid_input":
57030
58157
  return {
57031
58158
  cause: "The arguments failed validation or named an unsupported venue/method; the provider was not the problem.",
57032
- next_action: `Fix the arguments: call get_api_endpoint_schema for '${tool96}', use supported values, then retry. Do not retry unchanged.`
58159
+ next_action: `Fix the arguments: call get_api_endpoint_schema for '${tool100}', use supported values, then retry. Do not retry unchanged.`
57033
58160
  };
57034
58161
  case "missing_key":
57035
58162
  return {
57036
- cause: `${provider} is not configured on this deployment (no API credential), so '${tool96}' cannot return data here.`,
57037
- next_action: `Do not retry '${tool96}'. Use search_tools to pick a sibling provider tool for the same data, or ask the operator to configure ${provider} credentials.`
58163
+ cause: `${provider} is not configured on this deployment (no API credential), so '${tool100}' cannot return data here.`,
58164
+ next_action: `Do not retry '${tool100}'. Use search_tools to pick a sibling provider tool for the same data, or ask the operator to configure ${provider} credentials.`
57038
58165
  };
57039
58166
  case "plan_required":
57040
58167
  return {
57041
58168
  cause: `This ${provider} endpoint needs a paid plan, quota, or credits that this deployment does not have.`,
57042
- next_action: "Use search_tools to pick an alternative provider tool for the same data, or upgrade the plan. Retrying unchanged will fail again."
58169
+ next_action: "Use search_tools to pick an alternative provider tool for the same data. Retrying unchanged will fail again."
57043
58170
  };
57044
58171
  case "rate_limited":
57045
58172
  return {
@@ -57053,7 +58180,7 @@ function agentErrorCopy(status, context = {}) {
57053
58180
  };
57054
58181
  case "failing":
57055
58182
  return {
57056
- cause: `${provider} returned an unexpected error for '${tool96}'.`,
58183
+ cause: `${provider} returned an unexpected error for '${tool100}'.`,
57057
58184
  next_action: "Do not retry in a loop. Try the route fallback tool or a sibling provider via search_tools, and report the runtime_status to the user."
57058
58185
  };
57059
58186
  }
@@ -57613,17 +58740,17 @@ function fitPayloadWithinReceiptBudget(value, hive, isError = false) {
57613
58740
  const payloadBudget = Math.max(256, CHARACTER_LIMIT - receiptLength + 1);
57614
58741
  const truncated = smartTruncate(JSON.stringify(payload), payloadBudget);
57615
58742
  const parsed = parseToolText(truncated);
57616
- let boundedPayload = toHiveResultPayload(parsed);
57617
- hive.result_digest = digestHiveCanonicalJson(boundedPayload);
57618
- if (JSON.stringify({ ...boundedPayload, _hive: hive }).length > CHARACTER_LIMIT) {
57619
- boundedPayload = {
58743
+ let boundedPayload2 = toHiveResultPayload(parsed);
58744
+ hive.result_digest = digestHiveCanonicalJson(boundedPayload2);
58745
+ if (JSON.stringify({ ...boundedPayload2, _hive: hive }).length > CHARACTER_LIMIT) {
58746
+ boundedPayload2 = {
57620
58747
  _truncated: true,
57621
58748
  _original_length: JSON.stringify(payload).length,
57622
58749
  _note: "Response omitted because the provider payload plus its execution receipt exceeded the response limit. Re-call with narrower parameters or filters."
57623
58750
  };
57624
- hive.result_digest = digestHiveCanonicalJson(boundedPayload);
58751
+ hive.result_digest = digestHiveCanonicalJson(boundedPayload2);
57625
58752
  }
57626
- return boundedPayload;
58753
+ return boundedPayload2;
57627
58754
  }
57628
58755
  function createHiveExecutionMetadata(endpoint, durationMs, runtimeStatus, source, warnings = [], exec, integrity = {}, credit = {}) {
57629
58756
  const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -58162,6 +59289,17 @@ function executionGateFailureResult(endpoint, gate, durationMs, args, credit = {
58162
59289
  };
58163
59290
  }
58164
59291
  async function executeEndpoint(endpoint, args, context) {
59292
+ const result2 = await executeEndpointUnchecked(endpoint, args, context);
59293
+ if (result2.mcpResult.isError && endpoint.tool.outputSchema && result2.mcpResult.structuredContent !== void 0 && validateJsonSchema(
59294
+ result2.mcpResult.structuredContent,
59295
+ endpoint.tool.outputSchema
59296
+ ).length > 0) {
59297
+ const { structuredContent: _omitted, ...textOnly } = result2.mcpResult;
59298
+ return { ...result2, mcpResult: textOnly };
59299
+ }
59300
+ return result2;
59301
+ }
59302
+ async function executeEndpointUnchecked(endpoint, args, context) {
58165
59303
  const start = context.startMs ?? Date.now();
58166
59304
  const validationErrors = validateEndpointArgs(endpoint, args);
58167
59305
  if (validationErrors.length > 0) {
@@ -58277,15 +59415,22 @@ async function executeEndpoint(endpoint, args, context) {
58277
59415
  const guardLimit = getAnnotatedMaxResultSizeChars(endpoint.tool) ?? DEFAULT_RESULT_SIZE_GUARD_CHARS;
58278
59416
  const successText = getTextContent(normalized);
58279
59417
  if (successText && successText.length > guardLimit) {
59418
+ const guardParams = inputSchemaParamNames(endpoint.tool.inputSchema);
58280
59419
  bounded = {
58281
59420
  ...normalized,
58282
59421
  content: [
58283
- { type: "text", text: smartTruncate(successText, guardLimit) }
59422
+ {
59423
+ type: "text",
59424
+ text: smartTruncate(successText, guardLimit, {
59425
+ params: guardParams,
59426
+ offset: args?.offset
59427
+ })
59428
+ }
58284
59429
  ]
58285
59430
  };
58286
59431
  execForHive = { ...exec, truncated: true };
58287
59432
  guardWarnings.push(
58288
- `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.`
59433
+ `Response exceeded ${guardLimit} characters and was truncated for model-context safety. ${truncationRecallHint(guardParams)}`
58289
59434
  );
58290
59435
  }
58291
59436
  }
@@ -58498,7 +59643,7 @@ var CHAIN_ID_NAMES = {};
58498
59643
  for (const [name, id] of Object.entries(GOPLUS_CHAIN_IDS)) {
58499
59644
  if (!(id in CHAIN_ID_NAMES)) CHAIN_ID_NAMES[id] = name;
58500
59645
  }
58501
- var CHAIN_ALIASES = [
59646
+ var CHAIN_ALIASES2 = [
58502
59647
  "chain",
58503
59648
  "network",
58504
59649
  "chain_id",
@@ -58535,7 +59680,7 @@ var HERO_ARG_ALIASES = {
58535
59680
  "name"
58536
59681
  ],
58537
59682
  address: CONTRACT_ADDRESS_ALIASES,
58538
- chain: CHAIN_ALIASES,
59683
+ chain: CHAIN_ALIASES2,
58539
59684
  vs_currency: [
58540
59685
  "vs_currency",
58541
59686
  "vsCurrency",
@@ -58548,7 +59693,7 @@ var HERO_ARG_ALIASES = {
58548
59693
  },
58549
59694
  check_token_safety: {
58550
59695
  address: [...CONTRACT_ADDRESS_ALIASES, "token"],
58551
- chain: CHAIN_ALIASES,
59696
+ chain: CHAIN_ALIASES2,
58552
59697
  response_format: RESPONSE_FORMAT_ALIASES
58553
59698
  },
58554
59699
  get_wallet_portfolio: {
@@ -58561,7 +59706,8 @@ var HERO_ARG_ALIASES = {
58561
59706
  "account_address",
58562
59707
  "owner"
58563
59708
  ],
58564
- chain: CHAIN_ALIASES,
59709
+ chain: CHAIN_ALIASES2,
59710
+ include_spam: ["include_spam", "includeSpam", "show_spam"],
58565
59711
  response_format: RESPONSE_FORMAT_ALIASES
58566
59712
  }
58567
59713
  };
@@ -58848,9 +59994,10 @@ function heroTools(endpoints, options = {}) {
58848
59994
  address,
58849
59995
  chain: chainLabel,
58850
59996
  risk_flags: flags,
59997
+ ...isRecord3(entry2) ? { summary: summarizeTokenSafety(entry2) } : {},
58851
59998
  ...isRecord3(entry2) && entry2.holder_count !== void 0 ? { holder_count: entry2.holder_count } : {},
58852
59999
  ...isRecord3(entry2) && entry2.token_name !== void 0 ? { token_name: entry2.token_name } : {},
58853
- note: "risk_flags are raw GoPlus datapoints; treat '1' as flagged. Use response_format=detailed for the full report."
60000
+ 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."
58854
60001
  };
58855
60002
  };
58856
60003
  const checkTokenSafety = {
@@ -58862,7 +60009,7 @@ function heroTools(endpoints, options = {}) {
58862
60009
  tool: {
58863
60010
  name: "check_token_safety",
58864
60011
  title: "Check Token Safety",
58865
- 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.",
60012
+ 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.",
58866
60013
  inputSchema: zodToInputSchema(tokenSafetySchema),
58867
60014
  outputSchema: heroOutputSchema("check_token_safety"),
58868
60015
  annotations: VOLATILE_READ_ANNOTATIONS
@@ -58906,9 +60053,13 @@ function heroTools(endpoints, options = {}) {
58906
60053
  chain: z2.string().trim().max(40).optional().describe(
58907
60054
  'Chain name, for example "eth", "base", "arbitrum", or "solana". Defaults to eth for 0x addresses; Solana is auto-detected.'
58908
60055
  ),
60056
+ include_spam: z2.boolean().optional().describe(
60057
+ "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."
60058
+ ),
58909
60059
  response_format: RESPONSE_FORMAT
58910
60060
  });
58911
60061
  const MAX_CONCISE_TOKENS = 15;
60062
+ 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;
58912
60063
  const NATIVE_SYMBOLS = {
58913
60064
  eth: "ETH",
58914
60065
  ethereum: "ETH",
@@ -58948,10 +60099,10 @@ function heroTools(endpoints, options = {}) {
58948
60099
  };
58949
60100
  const usdFromPrices = (token, balance) => {
58950
60101
  if (!Array.isArray(token.tokenPrices)) return null;
58951
- const usd = token.tokenPrices.find(
60102
+ const usd2 = token.tokenPrices.find(
58952
60103
  (price) => isRecord3(price) && price.currency === "usd"
58953
60104
  );
58954
- const rate = usd && isRecord3(usd) ? Number(usd.value) : NaN;
60105
+ const rate = usd2 && isRecord3(usd2) ? Number(usd2.value) : NaN;
58955
60106
  const quantity = balance == null ? NaN : Number(balance);
58956
60107
  const value = rate * quantity;
58957
60108
  if (!Number.isFinite(value)) return null;
@@ -58968,44 +60119,72 @@ function heroTools(endpoints, options = {}) {
58968
60119
  }
58969
60120
  return [];
58970
60121
  };
58971
- const trimWalletPortfolio = (address, chainLabel) => (payload) => {
60122
+ const trimWalletPortfolio = (address, chainLabel, includeSpam = false, priced = false) => (payload) => {
58972
60123
  const walletLower = address.trim().toLowerCase();
58973
60124
  const tokens = firstArray(payload, ["tokens", "items", "balances"]);
58974
60125
  const summarized = tokens.map((token) => {
58975
60126
  if (!isRecord3(token)) return token;
58976
- const metadata100 = isRecord3(token.tokenMetadata) ? token.tokenMetadata : isRecord3(token.token) ? token.token : void 0;
60127
+ const metadata104 = isRecord3(token.tokenMetadata) ? token.tokenMetadata : isRecord3(token.token) ? token.token : void 0;
58977
60128
  const fallbackAddress = typeof token.address === "string" && token.address.trim().toLowerCase() === walletLower ? null : token.address ?? null;
58978
60129
  const tokenAddress = token.tokenAddress ?? token.mint ?? fallbackAddress;
58979
60130
  const isNative = tokenAddress == null;
58980
- const rawDecimals = token.decimals ?? metadata100?.decimals;
60131
+ const rawDecimals = token.decimals ?? metadata104?.decimals;
58981
60132
  const decimals = typeof rawDecimals === "number" ? rawDecimals : isNative ? 18 : void 0;
58982
60133
  const balance = decodeBalance(
58983
60134
  token.balance ?? token.amount ?? token.tokenBalance ?? token.uiAmount ?? null,
58984
60135
  decimals
58985
60136
  );
58986
60137
  return {
58987
- symbol: token.symbol ?? metadata100?.symbol ?? token.tokenSymbol ?? (isNative ? NATIVE_SYMBOLS[chainLabel] ?? null : null),
58988
- name: token.name ?? metadata100?.name ?? null,
60138
+ symbol: token.symbol ?? metadata104?.symbol ?? token.tokenSymbol ?? (isNative ? NATIVE_SYMBOLS[chainLabel] ?? null : null),
60139
+ name: token.name ?? metadata104?.name ?? null,
58989
60140
  address: tokenAddress,
58990
60141
  balance,
58991
- usd_value: token.totalValueUsd ?? token.valueUsd ?? token.usd_value ?? (typeof token.totalValueUsdCents === "number" ? token.totalValueUsdCents / 100 : usdFromPrices(token, balance))
60142
+ usd_value: token.totalValueUsd ?? token.valueUsd ?? token.usd_value ?? (typeof token.totalValueUsdCents === "number" ? token.totalValueUsdCents / 100 : usdFromPrices(token, balance)),
60143
+ ...token.h24_change_pct != null ? { h24_change_pct: token.h24_change_pct } : {}
58992
60144
  };
58993
60145
  });
58994
- const held = summarized.filter(
60146
+ const nonZero = summarized.filter(
58995
60147
  (token) => !isRecord3(token) || token.balance !== "0"
58996
60148
  );
60149
+ const isSpam = (token) => isRecord3(token) && token.address != null && (SPAM_LURE.test(`${token.name ?? ""} ${token.symbol ?? ""}`) || priced && token.usd_value == null);
60150
+ const held = includeSpam ? nonZero : nonZero.filter((t) => !isSpam(t));
60151
+ const hidden = nonZero.length - held.length;
58997
60152
  return {
58998
60153
  address,
58999
60154
  chain: chainLabel,
59000
60155
  token_count: held.length,
60156
+ ...hidden > 0 ? {
60157
+ spam_hidden: {
60158
+ count: hidden,
60159
+ reason: priced ? "no market price, or a URL/claim lure in the name" : "a URL/claim lure in the name",
60160
+ note: "Likely spam airdrops; pass include_spam=true to show them."
60161
+ }
60162
+ } : {},
59001
60163
  tokens: held.slice(0, MAX_CONCISE_TOKENS),
59002
60164
  ...held.length > MAX_CONCISE_TOKENS ? {
59003
60165
  note: `Showing the first ${MAX_CONCISE_TOKENS} of ${held.length} held tokens (zero balances omitted). Use response_format=detailed for the full provider payload.`
59004
- } : summarized.length > held.length ? {
59005
- note: `${summarized.length - held.length} zero-balance tokens omitted. Use response_format=detailed for the full provider payload.`
60166
+ } : summarized.length > nonZero.length ? {
60167
+ note: `${summarized.length - nonZero.length} zero-balance tokens omitted. Use response_format=detailed for the full provider payload.`
59006
60168
  } : {}
59007
60169
  };
59008
60170
  };
60171
+ const coingeckoBalances = (payload) => {
60172
+ const attributes = isRecord3(payload.data) && isRecord3(payload.data.attributes) ? payload.data.attributes : {};
60173
+ const rows = Array.isArray(attributes.balances) ? attributes.balances : [];
60174
+ const toNumber = (value) => value == null || !Number.isFinite(Number(value)) ? null : Number(value);
60175
+ return {
60176
+ total_value_usd: toNumber(attributes.total_value_usd),
60177
+ balances: rows.filter(isRecord3).map((row) => ({
60178
+ symbol: row.symbol ?? null,
60179
+ name: row.name ?? null,
60180
+ tokenAddress: row.token_type === "native" ? null : row.address ?? null,
60181
+ balance: row.balance ?? null,
60182
+ decimals: 0,
60183
+ usd_value: toNumber(row.value_usd),
60184
+ h24_change_pct: toNumber(row.h24_price_change_percentage)
60185
+ }))
60186
+ };
60187
+ };
59009
60188
  const getWalletPortfolio = {
59010
60189
  metadata: {
59011
60190
  resource: "dynamic_tools",
@@ -59015,7 +60194,7 @@ function heroTools(endpoints, options = {}) {
59015
60194
  tool: {
59016
60195
  name: "get_wallet_portfolio",
59017
60196
  title: "Get Wallet Portfolio",
59018
- 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.",
60197
+ description: `Token holdings for a wallet with balances, USD values, and 24h price change where available. Likely spam airdrops are hidden and counted (include_spam=true shows them). EVM chains route through CoinGecko (${WALLET_BALANCE_NETWORKS.join(", ")}) with Alchemy as the fallback; Solana wallets are detected automatically and route through Helius.`,
59019
60198
  inputSchema: zodToInputSchema(walletPortfolioSchema),
59020
60199
  outputSchema: heroOutputSchema("get_wallet_portfolio"),
59021
60200
  annotations: VOLATILE_READ_ANNOTATIONS
@@ -59031,20 +60210,43 @@ function heroTools(endpoints, options = {}) {
59031
60210
  `Invalid arguments: ${formatSchemaIssues(parsed.error)}`
59032
60211
  );
59033
60212
  }
59034
- const { address, chain } = parsed.data;
60213
+ const { address, chain, include_spam: includeSpam } = parsed.data;
59035
60214
  const format = heroFormat(parsed.data.response_format);
59036
60215
  if (looksLikeNameService(address)) return nameServiceError(address);
59037
60216
  if (isSolanaChain(chain, address)) {
59038
60217
  const result3 = await executeUnderlying("helius_get_wallet_balances", {
59039
60218
  wallet: address
59040
60219
  }, "get_wallet_portfolio");
59041
- return respond(result3, format, trimWalletPortfolio(address, "solana"));
60220
+ return respond(
60221
+ result3,
60222
+ format,
60223
+ trimWalletPortfolio(address, "solana", includeSpam)
60224
+ );
59042
60225
  }
59043
60226
  const normalized = normalizeChain(chain);
59044
60227
  const network = ALCHEMY_NETWORKS[normalized];
60228
+ const geckoNetwork = GECKO_NETWORKS[normalized] ?? normalized;
60229
+ let fallbackNote;
60230
+ if (WALLET_BALANCE_NETWORKS.includes(geckoNetwork)) {
60231
+ const result3 = await executeUnderlying("get_wallet_balances", {
60232
+ address,
60233
+ networks: geckoNetwork,
60234
+ per_page: 100
60235
+ }, "get_wallet_portfolio");
60236
+ if (!network || "hive" in result3 && result3.ok) {
60237
+ return respond(result3, format, (payload) => {
60238
+ const balances = coingeckoBalances(payload);
60239
+ return {
60240
+ ...trimWalletPortfolio(address, normalized, includeSpam, true)(balances),
60241
+ total_value_usd: balances.total_value_usd
60242
+ };
60243
+ });
60244
+ }
60245
+ fallbackNote = "CoinGecko wallet balances failed for this call; served from Alchemy.";
60246
+ }
59045
60247
  if (!network) {
59046
60248
  return errorResult5(
59047
- `Invalid arguments: Unknown chain '${chain}'. Pass an EVM chain name (${Object.keys(ALCHEMY_NETWORKS).slice(0, 6).join(", ")}, \u2026) or "solana".`
60249
+ `Invalid arguments: Unknown chain '${chain}'. Pass an EVM chain name (${Object.keys(ALCHEMY_NETWORKS).slice(0, 6).join(", ")}, \u2026), a CoinGecko network id (${WALLET_BALANCE_NETWORKS.join(", ")}), or "solana".`
59048
60250
  );
59049
60251
  }
59050
60252
  const result2 = await executeUnderlying("alchemy_get_tokens_by_wallet", {
@@ -59053,11 +60255,106 @@ function heroTools(endpoints, options = {}) {
59053
60255
  withMetadata: true,
59054
60256
  withPrices: true
59055
60257
  }, "get_wallet_portfolio");
59056
- return respond(result2, format, trimWalletPortfolio(address, normalized));
60258
+ return respond(result2, format, (payload) => ({
60259
+ ...trimWalletPortfolio(address, normalized, includeSpam, true)(payload),
60260
+ ...fallbackNote ? { fallback_note: fallbackNote } : {}
60261
+ }));
59057
60262
  }
59058
60263
  };
59059
60264
  return [getTokenPrice, checkTokenSafety, getWalletPortfolio];
59060
60265
  }
60266
+ var FLAG_VERDICTS = {
60267
+ is_honeypot: "Honeypot: GoPlus says this token cannot be sold",
60268
+ cannot_sell_all: "Holders cannot sell their whole balance",
60269
+ hidden_owner: "The contract has a hidden owner",
60270
+ can_take_back_ownership: "Ownership can be taken back after renouncing",
60271
+ is_mintable: "The owner can mint more supply",
60272
+ transfer_pausable: "Transfers can be paused",
60273
+ is_blacklisted: "The contract can blacklist addresses",
60274
+ owner_change_balance: "The owner can change balances",
60275
+ selfdestruct: "The contract can self-destruct",
60276
+ is_airdrop_scam: "GoPlus flags this as an airdrop scam"
60277
+ };
60278
+ function percentOf(value, fraction) {
60279
+ const n = typeof value === "string" || typeof value === "number" ? Number(value) : NaN;
60280
+ if (!Number.isFinite(n)) return null;
60281
+ return Math.round((fraction ? n * 100 : n) * 1e3) / 1e3;
60282
+ }
60283
+ function holderView(holder) {
60284
+ return {
60285
+ address: holder.address ?? holder.account ?? null,
60286
+ // Solana rows use account
60287
+ percent: percentOf(holder.percent, true),
60288
+ is_contract: holder.is_contract === 1 || holder.is_contract === "1",
60289
+ is_locked: holder.is_locked === 1 || holder.is_locked === "1",
60290
+ tag: holder.tag || null
60291
+ };
60292
+ }
60293
+ function summarizeTokenSafety(entry2) {
60294
+ const verdicts = [];
60295
+ for (const [key, sentence] of Object.entries(FLAG_VERDICTS)) {
60296
+ if (entry2[key] === "1") verdicts.push(sentence);
60297
+ }
60298
+ for (const side of ["buy", "sell"]) {
60299
+ const tax = percentOf(entry2[`${side}_tax`], true);
60300
+ if (tax !== null && tax >= 10) verdicts.push(`${side === "buy" ? "Buy" : "Sell"} tax is ${tax}%`);
60301
+ }
60302
+ const lpRows = Array.isArray(entry2.lp_holders) ? entry2.lp_holders.filter(isRecord3) : [];
60303
+ let lp = null;
60304
+ if (lpRows.length > 0) {
60305
+ const views = lpRows.map(holderView);
60306
+ const top = views.reduce((a, b) => (b.percent ?? 0) > (a.percent ?? 0) ? b : a);
60307
+ const locked = views.reduce((sum, v) => sum + (v.is_locked ? v.percent ?? 0 : 0), 0);
60308
+ lp = {
60309
+ holder_count: entry2.lp_holder_count ?? lpRows.length,
60310
+ locked_percent: Math.round(locked * 1e3) / 1e3,
60311
+ top_holder: top
60312
+ };
60313
+ if (locked < 50 && !top.is_locked && (top.percent ?? 0) >= 50) {
60314
+ verdicts.push(
60315
+ `LP not locked: ${top.percent}% held by one ${top.is_contract ? "contract" : "EOA"}`
60316
+ );
60317
+ } else if (locked < 50) {
60318
+ verdicts.push(`LP mostly unlocked: only ${Math.round(locked * 1e3) / 1e3}% of LP is locked`);
60319
+ }
60320
+ }
60321
+ const holderRows = Array.isArray(entry2.holders) ? entry2.holders.filter(isRecord3) : [];
60322
+ let topHolders = null;
60323
+ if (holderRows.length > 0) {
60324
+ const views = holderRows.map(holderView);
60325
+ const pairs = new Set(
60326
+ (Array.isArray(entry2.dex) ? entry2.dex.filter(isRecord3) : []).map((d) => String(d.pair ?? "").toLowerCase())
60327
+ );
60328
+ const owners = views.filter((v) => !pairs.has(String(v.address).toLowerCase()));
60329
+ const top10 = owners.slice(0, 10).reduce((sum, v) => sum + (v.percent ?? 0), 0);
60330
+ const largest = owners[0] ?? null;
60331
+ topHolders = {
60332
+ largest,
60333
+ top10_percent: Math.round(top10 * 1e3) / 1e3,
60334
+ excludes_dex_pairs: pairs.size > 0
60335
+ };
60336
+ if (largest && (largest.percent ?? 0) >= 20) {
60337
+ verdicts.push(
60338
+ `One address holds ${largest.percent}% of supply (${largest.is_contract ? "contract" : "EOA"}, ${largest.is_locked ? "locked" : "not locked"})`
60339
+ );
60340
+ }
60341
+ if (top10 >= 50) verdicts.push(`Top 10 holders hold ${Math.round(top10 * 10) / 10}% of supply`);
60342
+ }
60343
+ const creator = entry2.creator_address ? {
60344
+ address: entry2.creator_address,
60345
+ balance: entry2.creator_balance ?? null,
60346
+ percent: percentOf(entry2.creator_percent, true)
60347
+ } : null;
60348
+ const owner = entry2.owner_address ? {
60349
+ address: entry2.owner_address,
60350
+ balance: entry2.owner_balance ?? null,
60351
+ percent: percentOf(entry2.owner_percent, true)
60352
+ } : null;
60353
+ for (const [label, who] of [["Creator", creator], ["Owner", owner]]) {
60354
+ if (who && (who.percent ?? 0) >= 5) verdicts.push(`${label} still holds ${who.percent}% of supply`);
60355
+ }
60356
+ return { verdicts, lp, top_holders: topHolders, creator, owner };
60357
+ }
59061
60358
  function heroOutputSchema(name) {
59062
60359
  return HERO_TOOL_OUTPUT_SCHEMAS[name];
59063
60360
  }
@@ -59199,7 +60496,7 @@ var TASK_TOOLSET_ROUTES = {
59199
60496
  "holders",
59200
60497
  "Measure holder concentration with a bounded holder read.",
59201
60498
  "get_token_top_holders",
59202
- "moralis_get_token_holder_metrics",
60499
+ "get_token_holder_distribution",
59203
60500
  PROVIDER_FALLBACK
59204
60501
  ),
59205
60502
  step(
@@ -59225,7 +60522,7 @@ var TASK_TOOLSET_ROUTES = {
59225
60522
  step(
59226
60523
  "balances",
59227
60524
  "Read current wallet value and balances.",
59228
- "moralis_get_wallet_net_worth",
60525
+ "get_wallet_balances",
59229
60526
  "alchemy_get_token_balances_by_wallet",
59230
60527
  PROVIDER_FALLBACK
59231
60528
  ),
@@ -59264,7 +60561,7 @@ var TASK_TOOLSET_ROUTES = {
59264
60561
  step(
59265
60562
  "balances",
59266
60563
  "Anchor activity to the current wallet state.",
59267
- "moralis_get_wallet_net_worth",
60564
+ "get_wallet_balances",
59268
60565
  "alchemy_get_native_balance",
59269
60566
  PROVIDER_FALLBACK
59270
60567
  ),
@@ -59359,15 +60656,14 @@ var TASK_TOOLSET_ROUTES = {
59359
60656
  "simulation",
59360
60657
  "Simulate execution without broadcasting.",
59361
60658
  "tenderly_simulate_transaction",
59362
- "alchemy_simulate_execution",
60659
+ "tenderly_node_simulate_transaction",
59363
60660
  PROVIDER_FALLBACK
59364
60661
  ),
59365
60662
  step(
59366
60663
  "risk",
59367
60664
  "Inspect simulated asset changes.",
59368
- "alchemy_simulate_asset_changes",
59369
60665
  "simulate_evm_transaction",
59370
- PROVIDER_FALLBACK
60666
+ null
59371
60667
  )
59372
60668
  ],
59373
60669
  stopCondition: "Stop after decoding, execution outcome, and asset effects are clear; never broadcast or mutate chain state."
@@ -60191,6 +61487,7 @@ var TASK_TOOLSET_CATALOG = [
60191
61487
  optionalIdentifiers: ["symbol", "pair address", "time range"],
60192
61488
  recommendedTools: [
60193
61489
  "get_token_top_holders",
61490
+ "get_token_holder_distribution",
60194
61491
  "get_token_pools",
60195
61492
  "get_token_ohlcv",
60196
61493
  "get_token_info",
@@ -60242,18 +61539,18 @@ var TASK_TOOLSET_CATALOG = [
60242
61539
  requiredIdentifiers: ["wallet address", "chain"],
60243
61540
  optionalIdentifiers: ["token contract", "transaction hash", "time range"],
60244
61541
  recommendedTools: [
61542
+ "get_wallet_balances",
61543
+ "get_wallet_pnl",
61544
+ "get_wallet_trades",
61545
+ "get_wallet_transfers",
60245
61546
  "alchemy_get_tokens_by_wallet",
60246
61547
  "alchemy_get_token_balances_by_wallet",
60247
61548
  "alchemy_get_nfts_by_wallet",
60248
61549
  "alchemy_get_asset_transfers",
60249
61550
  "alchemy_get_native_balance",
60250
61551
  "moralis_get_wallet_history",
60251
- "moralis_get_wallet_token_balances",
60252
- "moralis_get_wallet_net_worth",
60253
- "moralis_get_wallet_profitability_summary",
60254
61552
  "moralis_get_wallet_defi_positions",
60255
61553
  "moralis_get_wallet_defi_summary",
60256
- "moralis_get_wallet_swaps",
60257
61554
  "moralis_get_wallet_chain_activity",
60258
61555
  "moralis_get_wallet_insight",
60259
61556
  "helius_get_wallet_balances",
@@ -60359,8 +61656,6 @@ var TASK_TOOLSET_CATALOG = [
60359
61656
  "alchemy_solana_get_compression_indexer_slot",
60360
61657
  "alchemy_solana_get_latest_compression_signatures",
60361
61658
  "alchemy_solana_get_latest_non_voting_signatures",
60362
- "alchemy_simulate_asset_changes",
60363
- "alchemy_simulate_execution",
60364
61659
  "simulate_evm_transaction",
60365
61660
  "defillama_get_hacks"
60366
61661
  ],
@@ -61590,7 +62885,11 @@ function getTaskOutputSchema(taskToolsetId) {
61590
62885
  }
61591
62886
 
61592
62887
  // src/services/execution-fallback.ts
61593
- var FALLBACK_TRIGGER_STATUSES = /* @__PURE__ */ new Set(["failing", "missing_key"]);
62888
+ var FALLBACK_TRIGGER_STATUSES = /* @__PURE__ */ new Set([
62889
+ "failing",
62890
+ "missing_key",
62891
+ "plan_required"
62892
+ ]);
61594
62893
  function findRouteFallback(primaryToolName) {
61595
62894
  for (const toolset of TASK_TOOLSETS) {
61596
62895
  if (toolset.fallbackTools.length === 0) continue;
@@ -61633,9 +62932,7 @@ async function executeEndpointWithRouteFallback(endpoints, endpoint, args, conte
61633
62932
  const fallbackProviderStatus = getProviderRuntimeStatus(
61634
62933
  getProviderDescriptorForEndpoint(fallback)
61635
62934
  );
61636
- if (fallbackProviderStatus === "missing_key" || fallbackProviderStatus === "failing") {
61637
- return primary;
61638
- }
62935
+ if (FALLBACK_TRIGGER_STATUSES.has(fallbackProviderStatus)) return primary;
61639
62936
  if (!argsMapCleanly(fallback, args)) return primary;
61640
62937
  const fallbackResult = await executeEndpoint(fallback, args, {
61641
62938
  ...context,
@@ -63033,7 +64330,7 @@ async function runMcpProtocolHealth(options = {}) {
63033
64330
  checks.push(
63034
64331
  await timedCheck("tools_list_root_surface", async () => {
63035
64332
  const listed = await client2.listTools();
63036
- const names = listed.tools.map((tool96) => tool96.name);
64333
+ const names = listed.tools.map((tool100) => tool100.name);
63037
64334
  const expectedCount = getRootVisibleToolCount();
63038
64335
  const expectedTools = [...ROOT_VISIBLE_TOOL_NAMES];
63039
64336
  if (listed.tools.length !== expectedCount) {
@@ -64421,7 +65718,7 @@ async function runOAuthLifecycleHealth(overrides = {}) {
64421
65718
  }
64422
65719
 
64423
65720
  // src/health/selfTest.ts
64424
- import Coingecko3 from "@coingecko/coingecko-typescript";
65721
+ import Coingecko4 from "@coingecko/coingecko-typescript";
64425
65722
 
64426
65723
  // src/tools/goplus/goplusClient.ts
64427
65724
  import { createHash as createHash8 } from "crypto";
@@ -65766,6 +67063,87 @@ var CCXTClient = class {
65766
67063
  }
65767
67064
  };
65768
67065
 
67066
+ // src/health/answerProbes.ts
67067
+ var ETH_WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
67068
+ var ETH_PEPE = "0x6982508145454Ce325dDbE47a25d4ec3d2311933";
67069
+ function isRecord4(value) {
67070
+ return !!value && typeof value === "object" && !Array.isArray(value);
67071
+ }
67072
+ function largestArray(value, depth = 0) {
67073
+ if (Array.isArray(value)) return value;
67074
+ if (!isRecord4(value) || depth > 4) return [];
67075
+ let best = [];
67076
+ for (const child of Object.values(value)) {
67077
+ const candidate = largestArray(child, depth + 1);
67078
+ if (candidate.length > best.length) best = candidate;
67079
+ }
67080
+ return best;
67081
+ }
67082
+ function rowsOf(payload) {
67083
+ return isRecord4(payload) && Array.isArray(payload.rows) ? payload.rows : largestArray(payload);
67084
+ }
67085
+ function returnedCountMismatch(payload, rows) {
67086
+ const returned = isRecord4(payload) ? payload.returnedCount : void 0;
67087
+ return typeof returned === "number" && returned !== rows.length ? `returnedCount ${returned} != rows.length ${rows.length}` : null;
67088
+ }
67089
+ var ANSWER_PROBES = {
67090
+ polymarket_search_markets: {
67091
+ // The "JD Vance wins 2028" market resolves 2028-11-07; the event-level
67092
+ // fixture in selfTest's TOOL_PROBE_ARGS points at the same market.
67093
+ args: { query: "vance 2028", limit: 3 },
67094
+ check: (payload) => {
67095
+ const rows = rowsOf(payload);
67096
+ if (rows.length === 0)
67097
+ return "text search for 'vance 2028' found no markets";
67098
+ const unmatched = rows.filter((row) => {
67099
+ const text = JSON.stringify(row).toLowerCase();
67100
+ return !text.includes("vance") || !text.includes("2028");
67101
+ });
67102
+ if (unmatched.length > 0) {
67103
+ return `${unmatched.length} of ${rows.length} rows do not match every query term`;
67104
+ }
67105
+ return returnedCountMismatch(payload, rows);
67106
+ }
67107
+ },
67108
+ get_token_top_holders: {
67109
+ args: { network: "eth", address: ETH_WETH, limit: 20 },
67110
+ check: (payload) => {
67111
+ const holders = largestArray(payload);
67112
+ return holders.length > 10 ? null : `asked for 20 WETH holders, got ${holders.length}`;
67113
+ }
67114
+ },
67115
+ rwa_get_hyperliquid_dex_perps: {
67116
+ args: { limit: 100 },
67117
+ check: (payload) => {
67118
+ const rows = rowsOf(payload);
67119
+ if (rows.length === 0) return "no HIP-3 builder-dex rows";
67120
+ return returnedCountMismatch(payload, rows);
67121
+ }
67122
+ },
67123
+ // check_token_safety builds its LP verdict on this GoPlus read.
67124
+ get_token_security: {
67125
+ args: { chainId: "1", contract_addresses: ETH_PEPE },
67126
+ check: (payload) => {
67127
+ const result2 = isRecord4(payload) ? payload.result : void 0;
67128
+ const token = isRecord4(result2) ? Object.values(result2).find(isRecord4) : void 0;
67129
+ if (!token) return "no token entry in the GoPlus result";
67130
+ const lpHolders = token.lp_holders;
67131
+ return Array.isArray(lpHolders) && lpHolders.length > 0 ? null : "PEPE came back without lp_holders";
67132
+ }
67133
+ }
67134
+ };
67135
+ function checkAnswer(probe, result2) {
67136
+ const text = isRecord4(result2) ? result2.content?.[0]?.text : void 0;
67137
+ if (typeof text !== "string") return "response has no text content";
67138
+ let payload;
67139
+ try {
67140
+ payload = JSON.parse(text);
67141
+ } catch {
67142
+ return "response is not JSON";
67143
+ }
67144
+ return probe.check(payload);
67145
+ }
67146
+
65769
67147
  // src/health/selfTest.ts
65770
67148
  function positiveTimeout(name, fallbackMs) {
65771
67149
  const configured = Number(process.env[name]);
@@ -65779,7 +67157,7 @@ var QUICK_PROBE_TIMEOUT = Math.min(
65779
67157
  var ETH_WALLET = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
65780
67158
  var MORALIS_EVM_WALLET = "0xF977814e90dA44bFA03b6295A0616a897441aceC";
65781
67159
  var ETH_USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
65782
- var ETH_WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
67160
+ var ETH_WETH2 = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
65783
67161
  var ETH_USDT = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
65784
67162
  var ETH_BAYC = "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D";
65785
67163
  var ETH_USDC_WETH_PAIR = "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc";
@@ -65836,6 +67214,7 @@ var PROVIDER_RESOURCE_PATTERNS = [
65836
67214
  ["polymarket", /^polymarket(?:[.-]|$)/],
65837
67215
  ["alternative", /^signals-fear-greed$/],
65838
67216
  ["blockchain", /^signals-onchain/],
67217
+ ["blockscout", /^blockscout(?:[.-]|$)/],
65839
67218
  ["fetch", /^fetch(?:[.-]|$)/],
65840
67219
  ["helius", /^helius\./],
65841
67220
  ["moralis", /^moralis\./],
@@ -65906,6 +67285,11 @@ var QUICK_PROBE_ARGS = {
65906
67285
  toolName: "signals_get_onchain_metrics",
65907
67286
  args: { metrics: ["hash_rate"], days: 1 }
65908
67287
  },
67288
+ // One token read plus one holder page on Ethereum (keyless public host).
67289
+ blockscout: {
67290
+ toolName: "get_token_holder_distribution",
67291
+ args: { network: "eth", address: ETH_USDC, max_pages: 1 }
67292
+ },
65909
67293
  fetch: {
65910
67294
  toolName: "fetch_public_api",
65911
67295
  args: { host: "api.coingecko.com", path: "/api/v3/ping" }
@@ -65944,7 +67328,7 @@ var FULL_MODE_DEFAULT_ARGS = {
65944
67328
  vs_currency: "usd",
65945
67329
  vs_currencies: "usd",
65946
67330
  network: "eth",
65947
- address: ETH_WETH,
67331
+ address: ETH_WETH2,
65948
67332
  days: "7"
65949
67333
  },
65950
67334
  defillama: {
@@ -66026,7 +67410,11 @@ var TOOL_PROBE_ARGS = {
66026
67410
  get_rwa_market_chart: { id: "gold", days: "7" },
66027
67411
  get_rwa_issuer: { id: "ondo-tokenized-assets" },
66028
67412
  // Polymarket fixtures verified live 2026-09-16; the event ends 2028-11-07.
66029
- polymarket_search_markets: { query: "election", limit: 3 },
67413
+ get_token_holder_distribution: {
67414
+ network: "eth",
67415
+ address: ETH_USDC,
67416
+ max_pages: 1
67417
+ },
66030
67418
  polymarket_get_events: { slug: "presidential-election-winner-2028" },
66031
67419
  polymarket_get_market: {
66032
67420
  slug: "will-jd-vance-win-the-2028-us-presidential-election"
@@ -66210,6 +67598,10 @@ var TOOL_PROBE_ARGS = {
66210
67598
  },
66211
67599
  get_multi_tokens: { network: "eth", addresses: ETH_USDC },
66212
67600
  get_token_lists: { asset_platform_id: "ethereum" },
67601
+ get_wallet_balances: { address: ETH_WALLET, networks: "eth", per_page: 5 },
67602
+ get_wallet_pnl: { address: ETH_WALLET, networks: "eth", per_page: 5 },
67603
+ get_wallet_trades: { network: "eth", address: ETH_WALLET, per_page: 5 },
67604
+ get_wallet_transfers: { network: "eth", address: ETH_WALLET, per_page: 5 },
66213
67605
  coingecko_get_onchain_token_top_traders: {
66214
67606
  network: COINGECKO_TOP_TRADERS_NETWORK,
66215
67607
  address: COINGECKO_TOP_TRADERS_TOKEN,
@@ -67235,7 +68627,7 @@ function getClientForProvider(provider, clients) {
67235
68627
  }
67236
68628
  }
67237
68629
  function buildClients() {
67238
- const coinGeckoClient = new Coingecko3(getCoinGeckoClientOptions());
68630
+ const coinGeckoClient = new Coingecko4(getCoinGeckoClientOptions());
67239
68631
  return {
67240
68632
  coinGeckoClient,
67241
68633
  goplusClient: new GoPlusClient(
@@ -67481,7 +68873,7 @@ async function getEffectiveProbeArgs(toolName, provider, clients, args) {
67481
68873
  return { [idFixture.field]: id };
67482
68874
  });
67483
68875
  }
67484
- const toolFixture = TOOL_PROBE_ARGS[toolName];
68876
+ const toolFixture = ANSWER_PROBES[toolName]?.args ?? TOOL_PROBE_ARGS[toolName];
67485
68877
  if (toolFixture) return toolFixture;
67486
68878
  return projectArgsToToolSchema(
67487
68879
  FULL_MODE_DEFAULT_ARGS[provider] || {},
@@ -67573,6 +68965,16 @@ async function runEndpointProbe(endpoint, provider, clients, args, timeoutMs = P
67573
68965
  );
67574
68966
  }
67575
68967
  }
68968
+ const answerProbe = args ? void 0 : ANSWER_PROBES[toolName];
68969
+ const answerProblem = answerProbe ? checkAnswer(answerProbe, result2) : null;
68970
+ if (answerProblem) {
68971
+ return {
68972
+ name: toolName,
68973
+ status: "failed",
68974
+ duration,
68975
+ error: `Answer check failed: ${answerProblem}`
68976
+ };
68977
+ }
67576
68978
  return { name: toolName, status: "passed", duration };
67577
68979
  } catch (err) {
67578
68980
  const errorMsg = err instanceof Error ? err.message : "Unknown error";
@@ -68323,8 +69725,8 @@ var WORKFLOW_REGRESSION_CASES = [
68323
69725
  expectedTopToolset: "wallet_investigation",
68324
69726
  expectedToolsets: ["wallet_investigation"],
68325
69727
  expectedCategoryTool: "get_portfolio_wallet_endpoints",
68326
- expectedSchemaTool: "moralis_get_wallet_profitability_summary",
68327
- expectedProvider: "Moralis"
69728
+ expectedSchemaTool: "get_wallet_pnl",
69729
+ expectedProvider: "CoinGecko"
68328
69730
  },
68329
69731
  {
68330
69732
  id: "wallet-defi-pnl",
@@ -68343,8 +69745,8 @@ var WORKFLOW_REGRESSION_CASES = [
68343
69745
  expectedTopToolset: "wallet_investigation",
68344
69746
  expectedToolsets: ["wallet_investigation"],
68345
69747
  expectedCategoryTool: "get_portfolio_wallet_endpoints",
68346
- expectedSchemaTool: "moralis_get_wallet_swaps",
68347
- expectedProvider: "Moralis"
69748
+ expectedSchemaTool: "get_wallet_trades",
69749
+ expectedProvider: "CoinGecko"
68348
69750
  },
68349
69751
  {
68350
69752
  id: "malicious-address",
@@ -69627,6 +71029,11 @@ var TOOLSET_INTENT_HINTS = {
69627
71029
  ],
69628
71030
  security_risk: [
69629
71031
  "rugpull",
71032
+ "rug pull",
71033
+ "rug check",
71034
+ "rugcheck",
71035
+ "honeypot",
71036
+ "scam",
69630
71037
  "safe to buy",
69631
71038
  "phishing",
69632
71039
  "malicious",
@@ -69986,6 +71393,9 @@ function catalogEntry(endpoint) {
69986
71393
  material: getMcpCreditCost(endpoint.tool.name) === 1,
69987
71394
  credit_cost: getMcpCreditCost(endpoint.tool.name),
69988
71395
  summary: summarizeToolDescription(endpoint.tool.description ?? ""),
71396
+ // Ranking text: the summary minus its negative guidance, so "Not
71397
+ // recommended for: ... holder info" never counts as a holder match.
71398
+ rankText: summarizeToolDescription(endpoint.tool.description ?? "").split(/not recommended for:|don't use when:|common mistakes:/i)[0].toLowerCase(),
69989
71399
  searchText: [
69990
71400
  endpoint.tool.name,
69991
71401
  endpoint.tool.title ?? getToolTitle(endpoint.tool.name),
@@ -70004,7 +71414,11 @@ function catalogEntry(endpoint) {
70004
71414
  };
70005
71415
  }
70006
71416
  function publicCatalogEntry(entry2) {
70007
- const { searchText: _searchText, ...publicEntry } = entry2;
71417
+ const {
71418
+ searchText: _searchText,
71419
+ rankText: _rankText,
71420
+ ...publicEntry
71421
+ } = entry2;
70008
71422
  return publicEntry;
70009
71423
  }
70010
71424
  function normalizeSearchToken(token) {
@@ -70044,31 +71458,36 @@ function matchesSearchTokens(haystack, query) {
70044
71458
  return tokens.some((token) => textMatchesSearchToken(haystack, token));
70045
71459
  }
70046
71460
  function queryMatchScore(entry2, query) {
70047
- if (!query) return 0;
71461
+ if (!query) return { score: 0, direct: true };
70048
71462
  if (!matchesSearchTokens(entry2.searchText, query)) return null;
70049
71463
  const tokens = parseSearchTokens(query);
70050
- if (tokens.length === 0) return 0;
71464
+ if (tokens.length === 0) return { score: 0, direct: true };
70051
71465
  const nameTitleText = [entry2.name, entry2.title].join(" ").toLowerCase();
71466
+ const summaryText = entry2.rankText;
70052
71467
  const coreText = [
70053
71468
  entry2.name,
70054
71469
  entry2.title,
70055
71470
  entry2.category ?? "",
70056
71471
  entry2.provider,
70057
- entry2.summary
71472
+ summaryText
70058
71473
  ].join(" ").toLowerCase();
70059
71474
  const nameTitleTokenSet = new Set(parseSearchTokens(nameTitleText));
70060
71475
  const coreTextTokenSet = new Set(parseSearchTokens(coreText));
71476
+ const providerTokenSet = new Set(parseSearchTokens(entry2.provider));
71477
+ let direct = false;
70061
71478
  const score = tokens.reduce((total, token) => {
70062
71479
  const exactToken = singularizeToken(normalizeSearchToken(token));
70063
- if (nameTitleTokenSet.has(exactToken)) return total + 24;
70064
- if (coreTextTokenSet.has(exactToken)) return total + 14;
70065
- if (textMatchesSearchToken(nameTitleText, token)) return total + 12;
70066
- if (textMatchesSearchToken(entry2.summary, token)) return total + 6;
70067
- if (textMatchesSearchToken(coreText, token)) return total + 4;
70068
- if (textMatchesSearchToken(entry2.searchText, token)) return total + 2;
71480
+ const providerBonus = providerTokenSet.has(exactToken) ? 8 : 0;
71481
+ const directScore = nameTitleTokenSet.has(exactToken) ? 24 : coreTextTokenSet.has(exactToken) ? 14 : nameTitleText.includes(exactToken) ? 12 : summaryText.includes(exactToken) ? 6 : coreText.includes(exactToken) ? 4 : 0;
71482
+ if (directScore > 0) {
71483
+ direct = true;
71484
+ return total + directScore + providerBonus;
71485
+ }
71486
+ if (textMatchesSearchToken(coreText, token)) return total + 2;
71487
+ if (textMatchesSearchToken(entry2.searchText, token)) return total + 1;
70069
71488
  return total;
70070
71489
  }, 0);
70071
- return score > 0 ? score : null;
71490
+ return score > 0 ? { score, direct } : null;
70072
71491
  }
70073
71492
  function buildProviderByToolName(endpoints) {
70074
71493
  const providerByToolName = /* @__PURE__ */ new Map();
@@ -70147,6 +71566,26 @@ function selectToolsetRoutes(routes, query, routeId) {
70147
71566
  const positive = ranked.filter((item) => item.score > 0);
70148
71567
  return (positive.length > 0 ? positive : ranked).slice(0, 1).map((item) => item.route);
70149
71568
  }
71569
+ function promoteHealthyRouteFallbacks(route, availabilityForTool) {
71570
+ let promoted = false;
71571
+ const steps = route.steps.map((step2) => {
71572
+ const primaryAvailability = availabilityForTool(step2.primaryTool);
71573
+ const fallbackTool = step2.fallback.tool;
71574
+ if (!isUnavailable(primaryAvailability) || !fallbackTool || isUnavailable(availabilityForTool(fallbackTool))) {
71575
+ return step2;
71576
+ }
71577
+ promoted = true;
71578
+ return {
71579
+ ...step2,
71580
+ primaryTool: fallbackTool,
71581
+ fallback: {
71582
+ tool: step2.primaryTool,
71583
+ when: `Promoted fallback: ${step2.primaryTool} is ${primaryAvailability} right now and returns runtime_status, not data. Original fallback condition: ${step2.fallback.when}`
71584
+ }
71585
+ };
71586
+ });
71587
+ return promoted ? { ...route, steps } : route;
71588
+ }
70150
71589
  function publicRouteEntry(route) {
70151
71590
  return {
70152
71591
  id: route.id,
@@ -70160,9 +71599,11 @@ function publicRouteEntry(route) {
70160
71599
  stopCondition: route.stopCondition
70161
71600
  };
70162
71601
  }
70163
- function publicToolsetEntry(entry2, detail, query, routeId) {
71602
+ function publicToolsetEntry(entry2, detail, query, routeId, availabilityForTool = () => "ok") {
70164
71603
  const routes = selectToolsetRoutes(entry2.routes, query, routeId).map(
70165
- publicRouteEntry
71604
+ (route) => publicRouteEntry(
71605
+ promoteHealthyRouteFallbacks(route, availabilityForTool)
71606
+ )
70166
71607
  );
70167
71608
  const compact = {
70168
71609
  id: entry2.id,
@@ -70193,6 +71634,11 @@ function publicToolsetEntry(entry2, detail, query, routeId) {
70193
71634
  }
70194
71635
  function toolsetQueryMatchScore(entry2, query) {
70195
71636
  if (!query) return 0;
71637
+ if (entry2.id === "solana_analysis" && /\b0x[0-9a-f]{40}\b|\b(ethereum|base|arbitrum|optimism|polygon|bsc|avalanche)\b/i.test(
71638
+ query
71639
+ ) && !/\b(solana|sol|spl|pump\.?fun|raydium|jupiter)\b/i.test(query)) {
71640
+ return null;
71641
+ }
70196
71642
  const haystack = [
70197
71643
  entry2.id,
70198
71644
  entry2.label,
@@ -70368,17 +71814,15 @@ function dynamicTools(endpoints, options = {}) {
70368
71814
  const filteredTools = catalogEntries.map((entry2, index) => ({
70369
71815
  entry: entry2,
70370
71816
  index,
70371
- score: routeId ? 100 : queryMatchScore(entry2, query)
70372
- })).filter((item) => item.score !== null).filter(
71817
+ match: routeId ? { score: 100, direct: true } : queryMatchScore(entry2, query)
71818
+ })).filter((item) => item.match !== null).filter(
70373
71819
  (item) => selectedRouteToolNames ? selectedRouteToolNames.has(item.entry.name) : true
70374
71820
  ).filter(
70375
71821
  (entry2) => provider ? entry2.entry.provider.toLowerCase() === provider : true
70376
71822
  ).filter(
70377
71823
  (entry2) => category ? entry2.entry.category?.toLowerCase() === category : true
70378
71824
  ).sort(
70379
- (a, b) => query ? b.score - a.score || a.index - b.index : a.index - b.index
70380
- ).sort(
70381
- (a, b) => Number(isUnavailable(availabilityFor(a.entry.provider))) - Number(isUnavailable(availabilityFor(b.entry.provider)))
71825
+ (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
70382
71826
  ).map((item) => item.entry);
70383
71827
  const filteredToolsets = taskToolsetCatalogEntries.map((entry2, index) => ({
70384
71828
  entry: entry2,
@@ -70432,7 +71876,8 @@ function dynamicTools(endpoints, options = {}) {
70432
71876
  entry2,
70433
71877
  parsed.detail ?? "compact",
70434
71878
  query,
70435
- routeId
71879
+ routeId,
71880
+ (toolName) => availabilityFor(providerByToolName.get(toolName) ?? "Hive")
70436
71881
  );
70437
71882
  const providerAvailability = Object.fromEntries(
70438
71883
  entry2.providers.map((name) => [
@@ -71258,7 +72703,7 @@ function getRootToolSet() {
71258
72703
  tools: Array.from(toolsByName.values()),
71259
72704
  visibleTools: ROOT_VISIBLE_TOOL_ORDER.map(
71260
72705
  (name) => compatibilityByName.get(name)
71261
- ).filter((tool96) => Boolean(tool96))
72706
+ ).filter((tool100) => Boolean(tool100))
71262
72707
  };
71263
72708
  secureLogger.info("Root MCP tool catalog cached", {
71264
72709
  actualToolCount: actualToolEndpoints.length,
@@ -71306,7 +72751,7 @@ function getSharedClients() {
71306
72751
  );
71307
72752
  }
71308
72753
  sharedClients = {
71309
- coinGeckoClient: new Coingecko4(getCoinGeckoClientOptions()),
72754
+ coinGeckoClient: new Coingecko5(getCoinGeckoClientOptions()),
71310
72755
  goplusClient,
71311
72756
  ccxtClient: new CCXTClient()
71312
72757
  };
@@ -71339,6 +72784,7 @@ function describeToolReference(toolName, endpointIndex) {
71339
72784
  };
71340
72785
  }
71341
72786
  function describeTaskToolset(toolset, endpointIndex) {
72787
+ const availability = getProviderAvailabilitySnapshot().byProviderName;
71342
72788
  return {
71343
72789
  id: toolset.id,
71344
72790
  kind: toolset.kind,
@@ -71347,7 +72793,12 @@ function describeTaskToolset(toolset, endpointIndex) {
71347
72793
  outcome: toolset.outcome,
71348
72794
  requiredIdentifiers: toolset.requiredIdentifiers,
71349
72795
  optionalIdentifiers: toolset.optionalIdentifiers,
71350
- routes: toolset.routes,
72796
+ routes: toolset.routes.map(
72797
+ (route) => promoteHealthyRouteFallbacks(route, (toolName) => {
72798
+ const endpoint = endpointIndex.get(toolName);
72799
+ return endpoint ? availability[getProviderNameForTool(endpoint)] ?? "ok" : "ok";
72800
+ })
72801
+ ),
71351
72802
  coverageCatalog: {
71352
72803
  recommendedTools: toolset.recommendedTools.map(
71353
72804
  (toolName) => describeToolReference(toolName, endpointIndex)
@@ -71524,21 +72975,21 @@ var HiveMCPServer = class {
71524
72975
  }
71525
72976
  registerAllTools() {
71526
72977
  const registeredTools = this.scope.kind === "root" ? this.compatibilityToolEndpoints.map((endpoint) => endpoint.tool) : this.visibleTools;
71527
- for (const tool96 of registeredTools) {
71528
- const inputSchema = tool96.inputSchema;
72978
+ for (const tool100 of registeredTools) {
72979
+ const inputSchema = tool100.inputSchema;
71529
72980
  const inputZodSchema = memoizedJsonSchemaToZodInputSchema(inputSchema);
71530
- const outputSchema = tool96.outputSchema;
72981
+ const outputSchema = tool100.outputSchema;
71531
72982
  const outputZodShape = outputSchema?.type === "object" ? memoizedJsonSchemaToZodOutputSchema(outputSchema) : void 0;
71532
- const toolName = tool96.name;
71533
- const toolMeta = tool96._meta;
72983
+ const toolName = tool100.name;
72984
+ const toolMeta = tool100._meta;
71534
72985
  this.server.registerTool(
71535
72986
  toolName,
71536
72987
  {
71537
- title: tool96.title,
71538
- description: tool96.description,
72988
+ title: tool100.title,
72989
+ description: tool100.description,
71539
72990
  inputSchema: inputZodSchema,
71540
72991
  ...outputZodShape && { outputSchema: outputZodShape },
71541
- annotations: tool96.annotations,
72992
+ annotations: tool100.annotations,
71542
72993
  // Surfaces response-size headroom annotations (plan 1.15) in
71543
72994
  // SDK-generated tools/list responses.
71544
72995
  ...toolMeta && { _meta: toolMeta }