hive-intelligence 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -10
- package/agent-skills/packs/hive-mcp/SKILL.md +13 -6
- package/agent-skills/packs/hive-mcp/references/client-install-matrix.md +6 -4
- package/agent-skills/packs/hive-mcp/references/clients.md +6 -3
- package/agent-skills/packs/hive-token-diligence/SKILL.md +2 -0
- package/build/{chunk-L4KVJDA7.js → chunk-5IQUEFCD.js} +13 -6
- package/build/{chunk-WQE3B64R.js → chunk-E2FAHUXM.js} +1 -1
- package/build/{chunk-MVO6LL3H.js → chunk-Z42XNN47.js} +1186 -234
- package/build/cli.js +23 -15
- package/build/config-audit-7GC42QX7.js +235 -0
- package/build/{doctor-O2OQX6EX.js → doctor-SMUVDZZ2.js} +61 -4
- package/build/{init-all-JXLG7LOX.js → init-all-SPEWION5.js} +9 -9
- package/build/{mcpServer-FXQPSDDC.js → mcpServer-T3DYYFCC.js} +1 -1
- package/build/monitor-worker.js +4 -3
- package/build/release.json +4 -4
- package/build/serve-2ICWEUMC.js +126 -0
- package/build/server.js +2 -2
- package/build/stdio.js +1 -1
- package/build/{tools-NJQPIJ53.js → tools-J6VPDGYP.js} +7 -3
- package/build/{upgrade-3NR3LAGU.js → upgrade-BJADUTO6.js} +1 -1
- package/package.json +12 -12
- package/build/serve-BCKB6DMI.js +0 -49
|
@@ -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(
|
|
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
|
-
-
|
|
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
|
|
4797
|
-
- Top
|
|
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: '
|
|
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,
|
|
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(
|
|
5327
|
+
return asTextContentResult(
|
|
5328
|
+
withTradeSummary(
|
|
5329
|
+
await client2.onchain.networks.tokens.trades.get(token_address, body),
|
|
5330
|
+
body.trade_volume_in_usd_greater_than
|
|
5331
|
+
)
|
|
5332
|
+
);
|
|
5259
5333
|
};
|
|
5260
5334
|
var get_tokens_networks_onchain_trades_default = { metadata: metadata50, tool: tool50, handler: handler50 };
|
|
5261
5335
|
|
|
@@ -18117,6 +18191,16 @@ var wrapper = {
|
|
|
18117
18191
|
oldToolName: "ccxt_fetch_convert_quote"
|
|
18118
18192
|
}
|
|
18119
18193
|
}
|
|
18194
|
+
],
|
|
18195
|
+
// Blockscout tools describe themselves; these entries only admit the
|
|
18196
|
+
// unprefixed name (src/tools/blockscout/tools.ts).
|
|
18197
|
+
getAllBlockscoutEndpoints: [
|
|
18198
|
+
{
|
|
18199
|
+
tool: {
|
|
18200
|
+
name: "get_token_holder_distribution",
|
|
18201
|
+
oldToolName: "get_token_holder_distribution"
|
|
18202
|
+
}
|
|
18203
|
+
}
|
|
18120
18204
|
]
|
|
18121
18205
|
};
|
|
18122
18206
|
|
|
@@ -46134,7 +46218,7 @@ function getAllAlchemyEndpoints() {
|
|
|
46134
46218
|
import axios3 from "axios";
|
|
46135
46219
|
var HYPERLIQUID_API_URL = "https://api.hyperliquid.xyz";
|
|
46136
46220
|
var DEFAULT_LEADERBOARD_LIMIT = 20;
|
|
46137
|
-
var MAX_LEADERBOARD_LIMIT =
|
|
46221
|
+
var MAX_LEADERBOARD_LIMIT = 500;
|
|
46138
46222
|
var FUNDING_HISTORY_PAGE_SIZE = 500;
|
|
46139
46223
|
var DEFAULT_FUNDING_HISTORY_RETURN_POINTS = 500;
|
|
46140
46224
|
var MAX_FUNDING_HISTORY_RETURN_POINTS = 500;
|
|
@@ -46177,7 +46261,7 @@ var HyperliquidAPI = class {
|
|
|
46177
46261
|
* Omit (or pass empty) for the main Hyperliquid DEX.
|
|
46178
46262
|
* @param limit - top N pairs by 24h notional volume (1-100, default 20).
|
|
46179
46263
|
*/
|
|
46180
|
-
async getPerpVolumeLeaderboard(dex, limit = DEFAULT_LEADERBOARD_LIMIT) {
|
|
46264
|
+
async getPerpVolumeLeaderboard(dex, limit = DEFAULT_LEADERBOARD_LIMIT, offset = 0) {
|
|
46181
46265
|
const body = { type: "metaAndAssetCtxs" };
|
|
46182
46266
|
if (dex) body.dex = dex;
|
|
46183
46267
|
const { data } = await this.client.post("/info", body);
|
|
@@ -46202,6 +46286,7 @@ var HyperliquidAPI = class {
|
|
|
46202
46286
|
Math.max(1, Math.floor(limit || DEFAULT_LEADERBOARD_LIMIT)),
|
|
46203
46287
|
MAX_LEADERBOARD_LIMIT
|
|
46204
46288
|
);
|
|
46289
|
+
const safeOffset = Math.max(0, Math.floor(offset || 0));
|
|
46205
46290
|
return {
|
|
46206
46291
|
dex: dex || "main",
|
|
46207
46292
|
pairCount: rows.length,
|
|
@@ -46209,7 +46294,8 @@ var HyperliquidAPI = class {
|
|
|
46209
46294
|
(sum, row) => sum + row.dayNotionalVolume,
|
|
46210
46295
|
0
|
|
46211
46296
|
),
|
|
46212
|
-
|
|
46297
|
+
offset: safeOffset,
|
|
46298
|
+
leaderboard: rows.slice(safeOffset, safeOffset + safeLimit)
|
|
46213
46299
|
};
|
|
46214
46300
|
}
|
|
46215
46301
|
/**
|
|
@@ -46438,6 +46524,85 @@ var HyperliquidAPI = class {
|
|
|
46438
46524
|
}
|
|
46439
46525
|
};
|
|
46440
46526
|
|
|
46527
|
+
// src/config/responseLimits.ts
|
|
46528
|
+
var RESPONSE_LIMITS = {
|
|
46529
|
+
// Maximum tokens allowed in a response (MCP constraint)
|
|
46530
|
+
MAX_RESPONSE_TOKENS: 25e3,
|
|
46531
|
+
// Estimated characters per token (rough approximation)
|
|
46532
|
+
CHARS_PER_TOKEN: 4,
|
|
46533
|
+
// Maximum characters allowed (25000 * 4 = 100,000)
|
|
46534
|
+
MAX_RESPONSE_CHARS: 1e5,
|
|
46535
|
+
// Default pagination limits for different endpoints
|
|
46536
|
+
DEFAULT_LIMITS: {
|
|
46537
|
+
tokens: 100,
|
|
46538
|
+
pools: 50,
|
|
46539
|
+
trades: 100,
|
|
46540
|
+
holders: 100,
|
|
46541
|
+
events: 200,
|
|
46542
|
+
nfts: 50
|
|
46543
|
+
},
|
|
46544
|
+
// Maximum allowed limits to prevent oversized responses
|
|
46545
|
+
MAX_LIMITS: {
|
|
46546
|
+
tokens: 100,
|
|
46547
|
+
pools: 50,
|
|
46548
|
+
trades: 200,
|
|
46549
|
+
holders: 200,
|
|
46550
|
+
events: 300,
|
|
46551
|
+
nfts: 100
|
|
46552
|
+
},
|
|
46553
|
+
// Size thresholds for automatic truncation (in characters)
|
|
46554
|
+
TRUNCATION_THRESHOLDS: {
|
|
46555
|
+
warning: 8e4,
|
|
46556
|
+
// Start warning at 80KB
|
|
46557
|
+
truncate: 1e5
|
|
46558
|
+
// Force truncation at 100KB
|
|
46559
|
+
}
|
|
46560
|
+
};
|
|
46561
|
+
|
|
46562
|
+
// src/utils/responseHeadroom.ts
|
|
46563
|
+
var MAX_RESULT_SIZE_META_KEY = "anthropic/maxResultSizeChars";
|
|
46564
|
+
var ANNOTATED_MAX_RESULT_SIZE_CHARS = RESPONSE_LIMITS.MAX_RESPONSE_CHARS;
|
|
46565
|
+
var DEFAULT_RESULT_SIZE_GUARD_CHARS = 2e4;
|
|
46566
|
+
var HEADROOM_TOOL_PATTERNS = [
|
|
46567
|
+
// Depth-of-book: order books, l2/l3 books.
|
|
46568
|
+
/orderbook|order_book|l2_book|l3_orderbook/i,
|
|
46569
|
+
// Holder / owner lists.
|
|
46570
|
+
/holders?(?:_|$)|owners_for/i,
|
|
46571
|
+
// OHLCV, candles, charts, and history/time-series families.
|
|
46572
|
+
/ohlc|candle|kline|market_chart|chart|history|timeseries/i
|
|
46573
|
+
];
|
|
46574
|
+
function toolNeedsResponseHeadroom(toolName) {
|
|
46575
|
+
return HEADROOM_TOOL_PATTERNS.some((pattern) => pattern.test(toolName));
|
|
46576
|
+
}
|
|
46577
|
+
function getAnnotatedMaxResultSizeChars(tool96) {
|
|
46578
|
+
const meta2 = tool96?._meta;
|
|
46579
|
+
const value = meta2?.[MAX_RESULT_SIZE_META_KEY];
|
|
46580
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
46581
|
+
}
|
|
46582
|
+
function withResponseHeadroomMeta(tool96) {
|
|
46583
|
+
if (!toolNeedsResponseHeadroom(tool96.name)) return tool96;
|
|
46584
|
+
const existing = tool96._meta;
|
|
46585
|
+
if (existing && existing[MAX_RESULT_SIZE_META_KEY] !== void 0) return tool96;
|
|
46586
|
+
return {
|
|
46587
|
+
...tool96,
|
|
46588
|
+
_meta: {
|
|
46589
|
+
...existing ?? {},
|
|
46590
|
+
[MAX_RESULT_SIZE_META_KEY]: ANNOTATED_MAX_RESULT_SIZE_CHARS
|
|
46591
|
+
}
|
|
46592
|
+
};
|
|
46593
|
+
}
|
|
46594
|
+
function largestFittingRowCount(max, fits) {
|
|
46595
|
+
if (max <= 0 || fits(max)) return Math.max(0, max);
|
|
46596
|
+
let lo = 1;
|
|
46597
|
+
let hi = max - 1;
|
|
46598
|
+
while (lo < hi) {
|
|
46599
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
46600
|
+
if (fits(mid)) lo = mid;
|
|
46601
|
+
else hi = mid - 1;
|
|
46602
|
+
}
|
|
46603
|
+
return lo;
|
|
46604
|
+
}
|
|
46605
|
+
|
|
46441
46606
|
// src/tools/hyperliquid/infoTools.ts
|
|
46442
46607
|
var MAX_ARRAY_ITEMS = 500;
|
|
46443
46608
|
var SPOT_TOKEN_TTL_MS = 10 * 60 * 1e3;
|
|
@@ -47376,14 +47541,19 @@ Hyperliquid is the dominant on-chain perpetual DEX. This reads the venue's own p
|
|
|
47376
47541
|
|
|
47377
47542
|
Args:
|
|
47378
47543
|
- dex (string, optional): Builder DEX name to scope the leaderboard to (e.g. "xyz" for tokenized-stock / pre-IPO perps like SPCX, MU, XYZ100). Omit for Hyperliquid's main perp DEX. Call hyperliquid_get_perp_dexs to discover builder DEX names.
|
|
47379
|
-
- limit (number, optional): Number of top pairs to return, ranked by 24h notional volume (1
|
|
47544
|
+
- limit (number, optional): Number of top pairs to return, ranked by 24h notional volume (1-${MAX_LEADERBOARD_LIMIT}, default 20). A page also stops at the ~${DEFAULT_RESULT_SIZE_GUARD_CHARS / 1e3}k-character response budget (about 100 pairs); page on with offset=next_offset to read every pair.
|
|
47545
|
+
- offset (number, optional): Rank to start from (0 = top pair, default 0).
|
|
47380
47546
|
|
|
47381
47547
|
Returns:
|
|
47382
47548
|
JSON object:
|
|
47383
47549
|
{
|
|
47384
47550
|
"dex": string, // "main" or the builder DEX name
|
|
47385
47551
|
"pairCount": number, // total pairs on this DEX
|
|
47386
|
-
"totalDayNotionalVolume": number, // summed 24h notional volume (USD)
|
|
47552
|
+
"totalDayNotionalVolume": number, // summed 24h notional volume over ALL pairs (USD), not just this page
|
|
47553
|
+
"offset": number, // rank of the first row
|
|
47554
|
+
"returnedCount": number, // rows in this response
|
|
47555
|
+
"next_offset": number | null, // pass as offset for the next page; null after the last pair
|
|
47556
|
+
"pagination_note"?: string, // present when the response budget cut the requested page short
|
|
47387
47557
|
"leaderboard": [{
|
|
47388
47558
|
"coin": string, // perp symbol (e.g. "BTC", "HYPE", "SPCX")
|
|
47389
47559
|
"dayNotionalVolume": number, // 24h notional volume (USD)
|
|
@@ -47414,8 +47584,13 @@ Don't use when:
|
|
|
47414
47584
|
limit: {
|
|
47415
47585
|
type: "integer",
|
|
47416
47586
|
minimum: 1,
|
|
47417
|
-
maximum:
|
|
47418
|
-
description:
|
|
47587
|
+
maximum: MAX_LEADERBOARD_LIMIT,
|
|
47588
|
+
description: `Top N pairs by 24h notional volume (1-${MAX_LEADERBOARD_LIMIT}, default 20). One page carries about 100 pairs (response budget); returnedCount and next_offset say what shipped.`
|
|
47589
|
+
},
|
|
47590
|
+
offset: {
|
|
47591
|
+
type: "integer",
|
|
47592
|
+
minimum: 0,
|
|
47593
|
+
description: "Rank to start from (0 = top pair). Pass the previous response's next_offset for the next page."
|
|
47419
47594
|
}
|
|
47420
47595
|
}
|
|
47421
47596
|
},
|
|
@@ -47424,8 +47599,29 @@ Don't use when:
|
|
|
47424
47599
|
handler: async (_client, args) => {
|
|
47425
47600
|
const dex = typeof args?.dex === "string" ? args.dex : void 0;
|
|
47426
47601
|
const limit = typeof args?.limit === "number" ? args.limit : void 0;
|
|
47427
|
-
const
|
|
47428
|
-
|
|
47602
|
+
const offset = typeof args?.offset === "number" ? args.offset : 0;
|
|
47603
|
+
const data = await api.getPerpVolumeLeaderboard(dex, limit, offset);
|
|
47604
|
+
const requested = data.leaderboard;
|
|
47605
|
+
const page = (kept) => {
|
|
47606
|
+
const next = data.offset + kept;
|
|
47607
|
+
return {
|
|
47608
|
+
...data,
|
|
47609
|
+
returnedCount: kept,
|
|
47610
|
+
next_offset: next < data.pairCount ? next : null,
|
|
47611
|
+
...kept < requested.length && {
|
|
47612
|
+
pagination_note: `${kept} of the ${requested.length} requested pairs fit the response budget. Re-call with offset=${next} for the next page.`
|
|
47613
|
+
},
|
|
47614
|
+
leaderboard: requested.slice(0, kept)
|
|
47615
|
+
};
|
|
47616
|
+
};
|
|
47617
|
+
return asTextContentResult(
|
|
47618
|
+
page(
|
|
47619
|
+
largestFittingRowCount(
|
|
47620
|
+
requested.length,
|
|
47621
|
+
(kept) => JSON.stringify(page(kept)).length <= DEFAULT_RESULT_SIZE_GUARD_CHARS - 500
|
|
47622
|
+
)
|
|
47623
|
+
)
|
|
47624
|
+
);
|
|
47429
47625
|
}
|
|
47430
47626
|
},
|
|
47431
47627
|
{
|
|
@@ -48599,6 +48795,7 @@ async function collectRwaRows(deps, venues = RWA_VENUE_IDS) {
|
|
|
48599
48795
|
// src/tools/rwa/tools.ts
|
|
48600
48796
|
var DEFAULT_LIMIT = 100;
|
|
48601
48797
|
var MAX_LIMIT = 500;
|
|
48798
|
+
var PAGE_BUDGET_CHARS = DEFAULT_RESULT_SIZE_GUARD_CHARS - 500;
|
|
48602
48799
|
var ASSET_CLASSES = [
|
|
48603
48800
|
"equity",
|
|
48604
48801
|
"pre_ipo",
|
|
@@ -48620,6 +48817,10 @@ function clampLimit2(value) {
|
|
|
48620
48817
|
if (!Number.isFinite(parsed)) return DEFAULT_LIMIT;
|
|
48621
48818
|
return Math.min(Math.max(parsed, 1), MAX_LIMIT);
|
|
48622
48819
|
}
|
|
48820
|
+
function clampOffset(value) {
|
|
48821
|
+
const parsed = typeof value === "number" ? Math.floor(value) : NaN;
|
|
48822
|
+
return Number.isFinite(parsed) ? Math.max(parsed, 0) : 0;
|
|
48823
|
+
}
|
|
48623
48824
|
function sortRows(rows) {
|
|
48624
48825
|
return [...rows].sort((a, b) => {
|
|
48625
48826
|
const oiA = a.open_interest_usd ?? -1;
|
|
@@ -48657,15 +48858,30 @@ function applyFilters(rows, args) {
|
|
|
48657
48858
|
}
|
|
48658
48859
|
};
|
|
48659
48860
|
}
|
|
48660
|
-
function
|
|
48861
|
+
function boundedPayload(rows, args, render) {
|
|
48661
48862
|
const sorted = sortRows(rows);
|
|
48662
|
-
const
|
|
48663
|
-
|
|
48664
|
-
|
|
48665
|
-
|
|
48666
|
-
|
|
48667
|
-
|
|
48863
|
+
const offset = clampOffset(args?.offset);
|
|
48864
|
+
const requested = sorted.slice(offset, offset + clampLimit2(args?.limit));
|
|
48865
|
+
const build = (kept) => {
|
|
48866
|
+
const next = offset + kept;
|
|
48867
|
+
const page = {
|
|
48868
|
+
count: sorted.length,
|
|
48869
|
+
offset,
|
|
48870
|
+
returnedCount: kept,
|
|
48871
|
+
truncated: next < sorted.length,
|
|
48872
|
+
next_offset: next < sorted.length ? next : null,
|
|
48873
|
+
...kept < requested.length && {
|
|
48874
|
+
pagination_note: `${kept} of the ${requested.length} requested rows fit the response budget. Re-call with offset=${next} for the next page.`
|
|
48875
|
+
}
|
|
48876
|
+
};
|
|
48877
|
+
return render(page, requested.slice(0, kept));
|
|
48668
48878
|
};
|
|
48879
|
+
return build(
|
|
48880
|
+
largestFittingRowCount(
|
|
48881
|
+
requested.length,
|
|
48882
|
+
(kept) => JSON.stringify(build(kept)).length <= PAGE_BUDGET_CHARS
|
|
48883
|
+
)
|
|
48884
|
+
);
|
|
48669
48885
|
}
|
|
48670
48886
|
var FILTER_PARAM_PROPERTIES = {
|
|
48671
48887
|
underlying: {
|
|
@@ -48685,7 +48901,12 @@ var FILTER_PARAM_PROPERTIES = {
|
|
|
48685
48901
|
type: "integer",
|
|
48686
48902
|
minimum: 1,
|
|
48687
48903
|
maximum: MAX_LIMIT,
|
|
48688
|
-
description: `Maximum rows to return (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}). Rows are ordered by open interest (deepest first).`
|
|
48904
|
+
description: `Maximum rows to return (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}). Rows are ordered by open interest (deepest first). A page also stops at the ~${DEFAULT_RESULT_SIZE_GUARD_CHARS / 1e3}k-character response budget (about 30 full rows); returnedCount is what shipped and next_offset continues.`
|
|
48905
|
+
},
|
|
48906
|
+
offset: {
|
|
48907
|
+
type: "integer",
|
|
48908
|
+
minimum: 0,
|
|
48909
|
+
description: "Rows to skip in the open-interest order (default 0). Pass the previous response's next_offset to read the next page."
|
|
48689
48910
|
}
|
|
48690
48911
|
};
|
|
48691
48912
|
function toMarketRow(row) {
|
|
@@ -48733,9 +48954,10 @@ Args:
|
|
|
48733
48954
|
- underlying (string, optional): filter to one canonical underlying ticker.
|
|
48734
48955
|
- asset_class (string, optional): filter to one asset class.
|
|
48735
48956
|
- limit (integer, optional): max rows (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}), deepest open interest first.
|
|
48957
|
+
- offset (integer, optional): rows to skip; pass next_offset to page.
|
|
48736
48958
|
|
|
48737
48959
|
Returns:
|
|
48738
|
-
Bounded JSON envelope: { "venue", "count", "returnedCount", "truncated", "rows": [normalized rows] }. ${returnsNote}
|
|
48960
|
+
Bounded JSON envelope: { "venue", "count", "offset", "returnedCount", "truncated", "next_offset", "pagination_note"?, "rows": [normalized rows] }. A page holds at most what fits the response budget (about 30 rows): returnedCount is the rows shipped, next_offset is null on the last page. ${returnsNote}
|
|
48739
48961
|
|
|
48740
48962
|
Use when:
|
|
48741
48963
|
${useWhen.map((line) => ` - ${line}`).join("\n")}
|
|
@@ -48747,7 +48969,8 @@ ${dontUseWhen.map((line) => ` - ${line}`).join("\n")}`,
|
|
|
48747
48969
|
properties: {
|
|
48748
48970
|
underlying: FILTER_PARAM_PROPERTIES.underlying,
|
|
48749
48971
|
asset_class: FILTER_PARAM_PROPERTIES.asset_class,
|
|
48750
|
-
limit: FILTER_PARAM_PROPERTIES.limit
|
|
48972
|
+
limit: FILTER_PARAM_PROPERTIES.limit,
|
|
48973
|
+
offset: FILTER_PARAM_PROPERTIES.offset
|
|
48751
48974
|
}
|
|
48752
48975
|
},
|
|
48753
48976
|
annotations: VOLATILE_READ_ANNOTATIONS
|
|
@@ -48756,13 +48979,15 @@ ${dontUseWhen.map((line) => ` - ${line}`).join("\n")}`,
|
|
|
48756
48979
|
const { rows, note } = await fetchRwaVenueRows(venueId, deps);
|
|
48757
48980
|
stampPriceSuspect(rows);
|
|
48758
48981
|
const { filtered, filters } = applyFilters(rows, args);
|
|
48759
|
-
|
|
48760
|
-
|
|
48761
|
-
|
|
48762
|
-
|
|
48763
|
-
|
|
48764
|
-
|
|
48765
|
-
|
|
48982
|
+
return asTextContentResult(
|
|
48983
|
+
boundedPayload(filtered, args, (page, pageRows) => ({
|
|
48984
|
+
venue: venueId,
|
|
48985
|
+
...note ? { note } : {},
|
|
48986
|
+
filters,
|
|
48987
|
+
...page,
|
|
48988
|
+
rows: pageRows
|
|
48989
|
+
}))
|
|
48990
|
+
);
|
|
48766
48991
|
}
|
|
48767
48992
|
};
|
|
48768
48993
|
}
|
|
@@ -48798,6 +49023,7 @@ Args:
|
|
|
48798
49023
|
- venue (string, optional): filter to one venue ("ostium", "avantis", "lighter", "extended", "hyperliquid", or "hyperliquid:xyz").
|
|
48799
49024
|
- asset_class (string, optional): filter to one asset class (equity, pre_ipo, etf, index, commodity, fx, crypto).
|
|
48800
49025
|
- limit (integer, optional): max rows (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}), deepest open interest first.
|
|
49026
|
+
- offset (integer, optional): rows to skip; pass next_offset to page.
|
|
48801
49027
|
|
|
48802
49028
|
Returns:
|
|
48803
49029
|
JSON object:
|
|
@@ -48805,9 +49031,11 @@ Returns:
|
|
|
48805
49031
|
"filters": { "underlying", "venue", "asset_class" },
|
|
48806
49032
|
"venueCount": number, "okCount": number,
|
|
48807
49033
|
"venues": [{ "venue", "runtime_status", "rowCount", "error"? }],
|
|
48808
|
-
"count": number, "returnedCount": number, "truncated": boolean,
|
|
49034
|
+
"count": number, "offset": number, "returnedCount": number, "truncated": boolean,
|
|
49035
|
+
"next_offset": number | null, "pagination_note"?: string,
|
|
48809
49036
|
"rows": [normalized funding rows]
|
|
48810
49037
|
}
|
|
49038
|
+
A page holds at most what fits the response budget (about 30 rows): returnedCount is the rows shipped; re-call with offset=next_offset for more.
|
|
48811
49039
|
|
|
48812
49040
|
Use when:
|
|
48813
49041
|
- "TSLA perp funding across venues" / tokenized-stock or equity perp funding questions
|
|
@@ -48827,17 +49055,19 @@ Don't use when:
|
|
|
48827
49055
|
handler: async (_client, args) => {
|
|
48828
49056
|
const { results, rows } = await collectRwaRows(deps);
|
|
48829
49057
|
const { filtered, filters } = applyFilters(rows, args);
|
|
48830
|
-
const envelope = boundedEnvelope(filtered, clampLimit2(args?.limit));
|
|
48831
49058
|
const okCount = results.filter(
|
|
48832
49059
|
(result2) => result2.runtime_status === "ok"
|
|
48833
49060
|
).length;
|
|
48834
|
-
return asTextContentResult(
|
|
48835
|
-
|
|
48836
|
-
|
|
48837
|
-
|
|
48838
|
-
|
|
48839
|
-
|
|
48840
|
-
|
|
49061
|
+
return asTextContentResult(
|
|
49062
|
+
boundedPayload(filtered, args, (page, pageRows) => ({
|
|
49063
|
+
filters,
|
|
49064
|
+
venueCount: results.length,
|
|
49065
|
+
okCount,
|
|
49066
|
+
venues: results,
|
|
49067
|
+
...page,
|
|
49068
|
+
rows: pageRows
|
|
49069
|
+
}))
|
|
49070
|
+
);
|
|
48841
49071
|
}
|
|
48842
49072
|
},
|
|
48843
49073
|
{
|
|
@@ -48864,6 +49094,7 @@ Args:
|
|
|
48864
49094
|
- venue (string, optional): filter to one venue.
|
|
48865
49095
|
- asset_class (string, optional): filter to one asset class (equity, pre_ipo, etf, index, commodity, fx, crypto).
|
|
48866
49096
|
- limit (integer, optional): max rows (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}), deepest open interest first.
|
|
49097
|
+
- offset (integer, optional): rows to skip; pass next_offset to page.
|
|
48867
49098
|
|
|
48868
49099
|
Returns:
|
|
48869
49100
|
JSON object:
|
|
@@ -48871,7 +49102,7 @@ Returns:
|
|
|
48871
49102
|
"filters": {...},
|
|
48872
49103
|
"venueCount": number, "okCount": number,
|
|
48873
49104
|
"venues": [{ "venue", "runtime_status", "rowCount", "error"? }],
|
|
48874
|
-
"count", "returnedCount", "truncated",
|
|
49105
|
+
"count", "offset", "returnedCount", "truncated", "next_offset", "pagination_note"?,
|
|
48875
49106
|
"rows": [{ "venue", "market", "symbol", "underlying", "asset_class", "funding_mechanism", "is_delisted", "mark_price", "oracle_price", "open_interest_usd", "volume_24h_usd", "is_price_suspect", "price_suspect_basis", "price_deviation_pct", "meta" }]
|
|
48876
49107
|
}
|
|
48877
49108
|
|
|
@@ -48892,20 +49123,19 @@ Don't use when:
|
|
|
48892
49123
|
handler: async (_client, args) => {
|
|
48893
49124
|
const { results, rows } = await collectRwaRows(deps);
|
|
48894
49125
|
const { filtered, filters } = applyFilters(rows, args);
|
|
48895
|
-
const envelope = boundedEnvelope(filtered, clampLimit2(args?.limit));
|
|
48896
49126
|
const okCount = results.filter(
|
|
48897
49127
|
(result2) => result2.runtime_status === "ok"
|
|
48898
49128
|
).length;
|
|
48899
|
-
return asTextContentResult(
|
|
48900
|
-
|
|
48901
|
-
|
|
48902
|
-
|
|
48903
|
-
|
|
48904
|
-
|
|
48905
|
-
|
|
48906
|
-
|
|
48907
|
-
|
|
48908
|
-
|
|
49129
|
+
return asTextContentResult(
|
|
49130
|
+
boundedPayload(filtered, args, (page, pageRows) => ({
|
|
49131
|
+
filters,
|
|
49132
|
+
venueCount: results.length,
|
|
49133
|
+
okCount,
|
|
49134
|
+
venues: results,
|
|
49135
|
+
...page,
|
|
49136
|
+
rows: pageRows.map(toMarketRow)
|
|
49137
|
+
}))
|
|
49138
|
+
);
|
|
48909
49139
|
}
|
|
48910
49140
|
},
|
|
48911
49141
|
// -----------------------------------------------------------------
|
|
@@ -49685,74 +49915,6 @@ async function fetchPolymarketJson(url, label, timeoutMs = POLYMARKET_TIMEOUT_MS
|
|
|
49685
49915
|
}
|
|
49686
49916
|
}
|
|
49687
49917
|
|
|
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
49918
|
// src/tools/polymarket/api.ts
|
|
49757
49919
|
var DEFAULT_LIMIT3 = 10;
|
|
49758
49920
|
var MAX_LIMIT3 = 50;
|
|
@@ -49760,9 +49922,14 @@ var MAX_OFFSET = 1e4;
|
|
|
49760
49922
|
var DEFAULT_BOOK_DEPTH = 10;
|
|
49761
49923
|
var MAX_BOOK_DEPTH = 50;
|
|
49762
49924
|
var MAX_HISTORY_POINTS = 500;
|
|
49763
|
-
var
|
|
49925
|
+
var SEARCH_EVENT_LIMIT = 20;
|
|
49764
49926
|
var EVENT_MARKETS_PREVIEW = 10;
|
|
49765
|
-
var EVENT_RESPONSE_BUDGET_CHARS =
|
|
49927
|
+
var EVENT_RESPONSE_BUDGET_CHARS = (
|
|
49928
|
+
// Rows only: the okResult envelope (odds note, source URL twice, filters)
|
|
49929
|
+
// adds ~1k, and at the full 20k the guard still trimmed 48 rows to 45
|
|
49930
|
+
// (order=volume, limit=50, 2026-10-09).
|
|
49931
|
+
DEFAULT_RESULT_SIZE_GUARD_CHARS - 2e3
|
|
49932
|
+
);
|
|
49766
49933
|
var DESCRIPTION_MAX_CHARS = 1200;
|
|
49767
49934
|
var PRICE_INTERVALS = ["1d", "1w", "1m", "max"];
|
|
49768
49935
|
var DEFAULT_FIDELITY_MINUTES = {
|
|
@@ -49962,6 +50129,21 @@ function parseInterval(value) {
|
|
|
49962
50129
|
}
|
|
49963
50130
|
return interval;
|
|
49964
50131
|
}
|
|
50132
|
+
var EVENT_ORDERS = [
|
|
50133
|
+
"volume24hr",
|
|
50134
|
+
"volume",
|
|
50135
|
+
"liquidity",
|
|
50136
|
+
"endDate"
|
|
50137
|
+
];
|
|
50138
|
+
function eventOrderParams(value, now = /* @__PURE__ */ new Date()) {
|
|
50139
|
+
const order = value === void 0 || value === null ? "volume24hr" : value;
|
|
50140
|
+
if (typeof order !== "string" || !EVENT_ORDERS.includes(order)) {
|
|
50141
|
+
invalid(
|
|
50142
|
+
`order must be one of ${EVENT_ORDERS.map((o) => `"${o}"`).join(", ")}; got ${JSON.stringify(value)}.`
|
|
50143
|
+
);
|
|
50144
|
+
}
|
|
50145
|
+
return order === "endDate" ? { order, ascending: true, end_date_min: now.toISOString() } : { order, ascending: false };
|
|
50146
|
+
}
|
|
49965
50147
|
function parseFidelity(value, interval) {
|
|
49966
50148
|
return parseBoundedInteger(
|
|
49967
50149
|
value,
|
|
@@ -50290,6 +50472,22 @@ async function fetchGammaEvents(params) {
|
|
|
50290
50472
|
const data = await fetchPolymarketJson(url, "Gamma /events");
|
|
50291
50473
|
return { url, rows: asRecordArray(data, "Gamma /events") };
|
|
50292
50474
|
}
|
|
50475
|
+
async function searchGammaMarkets(query, closed) {
|
|
50476
|
+
const url = buildUrl(GAMMA_BASE_URL, "/public-search", {
|
|
50477
|
+
q: query,
|
|
50478
|
+
limit_per_type: SEARCH_EVENT_LIMIT,
|
|
50479
|
+
events_status: closed ? "closed" : "active"
|
|
50480
|
+
});
|
|
50481
|
+
const data = asRecord(await fetchPolymarketJson(url, "Gamma /public-search"));
|
|
50482
|
+
const events = asRecordArray(data?.events ?? [], "Gamma /public-search");
|
|
50483
|
+
const rows = events.flatMap(
|
|
50484
|
+
(event) => (Array.isArray(event.markets) ? event.markets : []).map(asRecord).filter((market) => market !== null).map((market) => ({
|
|
50485
|
+
...market,
|
|
50486
|
+
events: [{ slug: event.slug, title: event.title }]
|
|
50487
|
+
}))
|
|
50488
|
+
);
|
|
50489
|
+
return { url, rows, eventCount: events.length };
|
|
50490
|
+
}
|
|
50293
50491
|
async function resolveMarket(selector) {
|
|
50294
50492
|
if (selector.slug) {
|
|
50295
50493
|
const { url, rows } = await fetchGammaMarkets({ slug: selector.slug });
|
|
@@ -50466,7 +50664,7 @@ function getAllPolymarketEndpoints() {
|
|
|
50466
50664
|
},
|
|
50467
50665
|
tool: {
|
|
50468
50666
|
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
|
|
50667
|
+
description: `Search Polymarket prediction markets by free text and list their market-implied odds, liquidity, volume, CLOB token ids, and end dates (Gamma API, keyless). With a query the tool runs Polymarket's own search across every event (up to ${SEARCH_EVENT_LIMIT} events, honoring the active/closed filters), keeps markets where every query term appears in the question, slug, bracket title, or event title, and orders them by 24h volume; when no market contains every term it returns Polymarket's relevance order instead and says so in scan.order. Without a query it returns the top markets by 24h volume.
|
|
50470
50668
|
|
|
50471
50669
|
Args:
|
|
50472
50670
|
- query (string, optional): free-text terms, all must match (case-insensitive), e.g. "fed rate" or "bitcoin 2026".
|
|
@@ -50477,7 +50675,7 @@ Args:
|
|
|
50477
50675
|
Returns:
|
|
50478
50676
|
${ENVELOPE_DOC}
|
|
50479
50677
|
"filters", "scan" { "scanned", "scan_limit", "matched", "order" }, and "rows": [{ "id", "slug", "question", "groupItemTitle", "conditionId", "eventSlug", "eventTitle", "outcomes", "outcomePrices" (0-1 per outcome, same order as outcomes), "clobTokenIds" (same order; pass to polymarket_get_orderbook / polymarket_get_price_history), "liquidity", "volume", "volume24hr", "endDate", "active", "closed" }].
|
|
50480
|
-
may_have_more is true when more rows matched than were returned or the
|
|
50678
|
+
scan.scan_limit is the event cap for a query and the row limit without one. may_have_more is true when more rows matched than were returned or the search page was full; narrow the query or use polymarket_get_events with a tag.
|
|
50481
50679
|
|
|
50482
50680
|
Examples:
|
|
50483
50681
|
- { "query": "fed decision", "limit": 5 } \u2014 Fed-rate brackets with current odds.
|
|
@@ -50514,16 +50712,20 @@ Don't use when:
|
|
|
50514
50712
|
const limit = parseLimit(args?.limit);
|
|
50515
50713
|
const active = parseOptionalBoolean(args?.active, "active", true);
|
|
50516
50714
|
const closed = parseOptionalBoolean(args?.closed, "closed", false);
|
|
50517
|
-
const scanLimit = query ?
|
|
50518
|
-
const
|
|
50519
|
-
|
|
50715
|
+
const scanLimit = query ? SEARCH_EVENT_LIMIT : limit;
|
|
50716
|
+
const search = query ? await searchGammaMarkets(query, closed) : null;
|
|
50717
|
+
const listing = search ?? await fetchGammaMarkets({
|
|
50718
|
+
limit,
|
|
50520
50719
|
active,
|
|
50521
50720
|
closed,
|
|
50522
50721
|
order: "volume24hr",
|
|
50523
50722
|
ascending: false
|
|
50524
50723
|
});
|
|
50525
|
-
const
|
|
50526
|
-
const
|
|
50724
|
+
const { url, rows } = listing;
|
|
50725
|
+
const mapped = rows.map(toMarketRow2).filter(
|
|
50726
|
+
(row) => !query || row.active === active && row.closed === closed
|
|
50727
|
+
);
|
|
50728
|
+
const exact = query ? mapped.filter(
|
|
50527
50729
|
(row) => matchesQuery(
|
|
50528
50730
|
[
|
|
50529
50731
|
row.question,
|
|
@@ -50534,19 +50736,22 @@ Don't use when:
|
|
|
50534
50736
|
],
|
|
50535
50737
|
query
|
|
50536
50738
|
)
|
|
50537
|
-
) : mapped;
|
|
50739
|
+
).sort((a, b) => (b.volume24hr ?? 0) - (a.volume24hr ?? 0)) : mapped;
|
|
50740
|
+
const fuzzy = Boolean(query) && exact.length === 0;
|
|
50741
|
+
const matched = fuzzy ? mapped : exact;
|
|
50742
|
+
const scanned = search ? search.eventCount : rows.length;
|
|
50538
50743
|
const items = matched.slice(0, limit);
|
|
50539
50744
|
return okResult(url, {
|
|
50540
50745
|
filters: { query, active, closed, limit },
|
|
50541
50746
|
scan: {
|
|
50542
|
-
scanned
|
|
50747
|
+
scanned,
|
|
50543
50748
|
scan_limit: scanLimit,
|
|
50544
50749
|
matched: matched.length,
|
|
50545
|
-
order: "volume24hr desc"
|
|
50750
|
+
order: fuzzy ? "relevance (no market contains every term)" : "volume24hr desc"
|
|
50546
50751
|
},
|
|
50547
50752
|
rows: items,
|
|
50548
50753
|
returnedCount: items.length,
|
|
50549
|
-
may_have_more: matched.length > limit ||
|
|
50754
|
+
may_have_more: matched.length > limit || scanned >= scanLimit
|
|
50550
50755
|
});
|
|
50551
50756
|
} catch (error) {
|
|
50552
50757
|
return errorResult3(toolName, error);
|
|
@@ -50568,14 +50773,16 @@ Args:
|
|
|
50568
50773
|
- slug (string, optional): one event's URL slug (exact); other filters are ignored.
|
|
50569
50774
|
- active (boolean, optional): Polymarket active flag filter (default true).
|
|
50570
50775
|
- closed (boolean, optional): Polymarket closed flag filter (default false).
|
|
50776
|
+
- order (string, optional): "volume24hr" (default, trending: most traded in the last 24h), "volume" (biggest by lifetime volume), "liquidity" (deepest books), or "endDate" (closing soonest, from now on). Biggest first for the volume and liquidity sorts.
|
|
50571
50777
|
${PAGING_ARGS_DOC}
|
|
50572
50778
|
|
|
50573
50779
|
Returns:
|
|
50574
50780
|
${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
|
|
50781
|
+
"filters" (including the order applied), "markets_preview_limit" (nested previews actually kept per event, ${EVENT_MARKETS_PREVIEW} unless the size budget forced fewer), "rows_dropped_for_size" (0 unless headline data alone overran the budget), and "rows": [{ "id", "slug", "title", "active", "closed", "endDate", "liquidity", "volume", "volume24hr", "negRisk", "tags", "marketCount", "markets": [{ "slug", "question", "groupItemTitle", "conditionId", "outcomes", "outcomePrices", "clobTokenIds", "volume24hr" }], "marketsTruncated" }]. Rows follow the requested order (24h volume, desc, by default). returnedCount is always the number of rows actually in the response. Large limits are slow because Gamma nests every market of every event.
|
|
50576
50782
|
|
|
50577
50783
|
Examples:
|
|
50578
50784
|
- { "tag": "crypto", "limit": 5 } \u2014 the busiest crypto events.
|
|
50785
|
+
- { "order": "volume", "limit": 5 } \u2014 the biggest events by all-time volume (not just today's trend).
|
|
50579
50786
|
- { "slug": "presidential-election-winner-2028" } \u2014 one event with its top-${EVENT_MARKETS_PREVIEW} candidates by odds.
|
|
50580
50787
|
- { "limit": 10, "offset": 10 } \u2014 the next page of the most active events.
|
|
50581
50788
|
|
|
@@ -50600,6 +50807,11 @@ Don't use when:
|
|
|
50600
50807
|
},
|
|
50601
50808
|
active: ACTIVE_SCHEMA,
|
|
50602
50809
|
closed: CLOSED_SCHEMA,
|
|
50810
|
+
order: {
|
|
50811
|
+
type: "string",
|
|
50812
|
+
enum: [...EVENT_ORDERS],
|
|
50813
|
+
description: 'Sort: "volume24hr" (default, trending), "volume" (biggest all-time), "liquidity", or "endDate" (closing soonest from now).'
|
|
50814
|
+
},
|
|
50603
50815
|
limit: LIMIT_SCHEMA,
|
|
50604
50816
|
offset: OFFSET_SCHEMA
|
|
50605
50817
|
}
|
|
@@ -50615,19 +50827,19 @@ Don't use when:
|
|
|
50615
50827
|
const offset = parseOffset(args?.offset);
|
|
50616
50828
|
const active = parseOptionalBoolean(args?.active, "active", true);
|
|
50617
50829
|
const closed = parseOptionalBoolean(args?.closed, "closed", false);
|
|
50830
|
+
const sort = eventOrderParams(args?.order);
|
|
50618
50831
|
const { url, rows } = slug ? await fetchGammaEvents({ slug }) : await fetchGammaEvents({
|
|
50619
50832
|
limit,
|
|
50620
50833
|
offset,
|
|
50621
50834
|
active,
|
|
50622
50835
|
closed,
|
|
50623
50836
|
tag_slug: tag ?? void 0,
|
|
50624
|
-
|
|
50625
|
-
ascending: false
|
|
50837
|
+
...sort
|
|
50626
50838
|
});
|
|
50627
50839
|
const bounded = boundEventRows(rows);
|
|
50628
50840
|
const items = bounded.rows;
|
|
50629
50841
|
return okResult(url, {
|
|
50630
|
-
filters: slug ? { slug } : { tag, active, closed, limit, offset },
|
|
50842
|
+
filters: slug ? { slug } : { tag, active, closed, order: sort.order, limit, offset },
|
|
50631
50843
|
markets_preview_limit: bounded.marketsPreviewLimit,
|
|
50632
50844
|
rows_dropped_for_size: bounded.rowsDropped,
|
|
50633
50845
|
rows: items,
|
|
@@ -51039,7 +51251,7 @@ function isAbortError(error) {
|
|
|
51039
51251
|
const name = error?.name;
|
|
51040
51252
|
return name === "AbortError" || name === "TimeoutError";
|
|
51041
51253
|
}
|
|
51042
|
-
async function fetchJsonWithTimeout(url, label, timeoutMs = SIGNALS_FETCH_TIMEOUT_MS) {
|
|
51254
|
+
async function fetchJsonWithTimeout(url, label, timeoutMs = SIGNALS_FETCH_TIMEOUT_MS, headers = {}) {
|
|
51043
51255
|
const controller = new AbortController();
|
|
51044
51256
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
51045
51257
|
try {
|
|
@@ -51049,7 +51261,8 @@ async function fetchJsonWithTimeout(url, label, timeoutMs = SIGNALS_FETCH_TIMEOU
|
|
|
51049
51261
|
signal: controller.signal,
|
|
51050
51262
|
headers: {
|
|
51051
51263
|
Accept: "application/json",
|
|
51052
|
-
"User-Agent": "hive-mcp-server/1.0.0"
|
|
51264
|
+
"User-Agent": "hive-mcp-server/1.0.0",
|
|
51265
|
+
...headers
|
|
51053
51266
|
}
|
|
51054
51267
|
});
|
|
51055
51268
|
} catch (error) {
|
|
@@ -52294,6 +52507,334 @@ Don't use when:
|
|
|
52294
52507
|
];
|
|
52295
52508
|
}
|
|
52296
52509
|
|
|
52510
|
+
// src/tools/blockscout/tools.ts
|
|
52511
|
+
import Coingecko2 from "@coingecko/coingecko-typescript";
|
|
52512
|
+
var BLOCKSCOUT_PROVIDER = "Blockscout";
|
|
52513
|
+
var HOLDERS_PAGE_SIZE = 50;
|
|
52514
|
+
var DEFAULT_MAX_PAGES = 20;
|
|
52515
|
+
var MAX_PAGES = 40;
|
|
52516
|
+
var TIME_BUDGET_MS = 2e4;
|
|
52517
|
+
var CHAINS = {
|
|
52518
|
+
eth: { chainId: 1, host: "eth.blockscout.com", gecko: "eth" },
|
|
52519
|
+
base: { chainId: 8453, host: "base.blockscout.com", gecko: "base" },
|
|
52520
|
+
optimism: { chainId: 10, host: "explorer.optimism.io", gecko: "optimism" },
|
|
52521
|
+
arbitrum: {
|
|
52522
|
+
chainId: 42161,
|
|
52523
|
+
host: "arbitrum.blockscout.com",
|
|
52524
|
+
gecko: "arbitrum"
|
|
52525
|
+
},
|
|
52526
|
+
polygon: {
|
|
52527
|
+
chainId: 137,
|
|
52528
|
+
host: "polygon.blockscout.com",
|
|
52529
|
+
gecko: "polygon_pos"
|
|
52530
|
+
}
|
|
52531
|
+
};
|
|
52532
|
+
var CHAIN_ALIASES = {
|
|
52533
|
+
ethereum: "eth",
|
|
52534
|
+
mainnet: "eth",
|
|
52535
|
+
"1": "eth",
|
|
52536
|
+
"8453": "base",
|
|
52537
|
+
op: "optimism",
|
|
52538
|
+
"10": "optimism",
|
|
52539
|
+
arb: "arbitrum",
|
|
52540
|
+
"42161": "arbitrum",
|
|
52541
|
+
matic: "polygon",
|
|
52542
|
+
polygon_pos: "polygon",
|
|
52543
|
+
"137": "polygon"
|
|
52544
|
+
};
|
|
52545
|
+
var USD_TIERS = [
|
|
52546
|
+
{ label: ">$10k", min_usd: 1e4, max_usd: null },
|
|
52547
|
+
{ label: "$1k-$10k", min_usd: 1e3, max_usd: 1e4 },
|
|
52548
|
+
{ label: "$100-$1k", min_usd: 100, max_usd: 1e3 },
|
|
52549
|
+
{ label: "$1-$100", min_usd: 1, max_usd: 100 },
|
|
52550
|
+
{ label: "<$1", min_usd: 0, max_usd: 1 }
|
|
52551
|
+
];
|
|
52552
|
+
function tallyTiers(rows, totalHolders, stop) {
|
|
52553
|
+
const reachedBottom = stop === "end_of_holders";
|
|
52554
|
+
const smallest = rows.length > 0 ? rows[rows.length - 1].usd : null;
|
|
52555
|
+
const tiers = USD_TIERS.map((tier) => {
|
|
52556
|
+
const members = rows.filter(
|
|
52557
|
+
(row) => row.usd >= tier.min_usd && (tier.max_usd === null || row.usd < tier.max_usd)
|
|
52558
|
+
);
|
|
52559
|
+
const exact = reachedBottom || smallest !== null && smallest < tier.min_usd;
|
|
52560
|
+
const reached = smallest !== null && smallest < (tier.max_usd ?? Infinity);
|
|
52561
|
+
return {
|
|
52562
|
+
...tier,
|
|
52563
|
+
count: exact || reached ? members.length : null,
|
|
52564
|
+
contracts: exact || reached ? members.filter((r) => r.is_contract).length : null,
|
|
52565
|
+
exact,
|
|
52566
|
+
lower_bound: !exact && reached
|
|
52567
|
+
};
|
|
52568
|
+
});
|
|
52569
|
+
const dust = tiers[tiers.length - 1];
|
|
52570
|
+
if (!dust.exact && stop === "below_smallest_tier" && totalHolders !== null) {
|
|
52571
|
+
const above = rows.filter((row) => row.usd >= 1).length;
|
|
52572
|
+
dust.count = Math.max(totalHolders - above, 0);
|
|
52573
|
+
dust.contracts = null;
|
|
52574
|
+
dust.exact = true;
|
|
52575
|
+
dust.lower_bound = false;
|
|
52576
|
+
}
|
|
52577
|
+
const real = tiers.filter((tier) => tier.min_usd >= 100);
|
|
52578
|
+
return {
|
|
52579
|
+
tiers,
|
|
52580
|
+
holders_100_usd_plus: {
|
|
52581
|
+
count: real.reduce((sum, tier) => sum + (tier.count ?? 0), 0),
|
|
52582
|
+
exact: real.every((tier) => tier.exact)
|
|
52583
|
+
},
|
|
52584
|
+
exhaustive: tiers.every((tier) => tier.exact),
|
|
52585
|
+
smallest_scanned_usd: smallest
|
|
52586
|
+
};
|
|
52587
|
+
}
|
|
52588
|
+
function parseArgs(args) {
|
|
52589
|
+
const rawNetwork = String(args?.network ?? args?.chain ?? "").trim().toLowerCase();
|
|
52590
|
+
const key = CHAIN_ALIASES[rawNetwork] ?? rawNetwork;
|
|
52591
|
+
const chain = CHAINS[key];
|
|
52592
|
+
if (!chain) {
|
|
52593
|
+
throw new SignalsInputError(
|
|
52594
|
+
`network must be one of ${Object.keys(CHAINS).join(", ")} (or a chain id); got ${JSON.stringify(args?.network ?? null)}.`
|
|
52595
|
+
);
|
|
52596
|
+
}
|
|
52597
|
+
const address = String(args?.address ?? "").trim();
|
|
52598
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
52599
|
+
throw new SignalsInputError(
|
|
52600
|
+
`address must be a 0x token contract address; got ${JSON.stringify(args?.address ?? null)}.`
|
|
52601
|
+
);
|
|
52602
|
+
}
|
|
52603
|
+
const priceOverride = args?.price_usd === void 0 || args?.price_usd === null ? null : parseNumberArg(args.price_usd, "price_usd", Number.MIN_VALUE, 1e12, 0);
|
|
52604
|
+
return {
|
|
52605
|
+
network: key,
|
|
52606
|
+
chain,
|
|
52607
|
+
address,
|
|
52608
|
+
maxPages: parseIntegerArg(
|
|
52609
|
+
args?.max_pages,
|
|
52610
|
+
"max_pages",
|
|
52611
|
+
1,
|
|
52612
|
+
MAX_PAGES,
|
|
52613
|
+
DEFAULT_MAX_PAGES
|
|
52614
|
+
),
|
|
52615
|
+
priceOverride
|
|
52616
|
+
};
|
|
52617
|
+
}
|
|
52618
|
+
function blockscoutGet(chain, path) {
|
|
52619
|
+
const apiKey = getEnvVar("BLOCKSCOUT_API_KEY");
|
|
52620
|
+
const url = apiKey ? `https://api.blockscout.com/${chain.chainId}/api/v2${path}` : `https://${chain.host}/api/v2${path}`;
|
|
52621
|
+
return fetchJsonWithTimeout(
|
|
52622
|
+
url,
|
|
52623
|
+
`Blockscout (${chain.chainId})`,
|
|
52624
|
+
void 0,
|
|
52625
|
+
apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
52626
|
+
).catch((error) => {
|
|
52627
|
+
if (error instanceof SignalsHttpError && error.status === 404) {
|
|
52628
|
+
throw new SignalsInputError(
|
|
52629
|
+
`Blockscout has no token at this address on chain ${chain.chainId}.`
|
|
52630
|
+
);
|
|
52631
|
+
}
|
|
52632
|
+
if (!apiKey && error instanceof SignalsHttpError && (error.status === 403 || error.status === 402)) {
|
|
52633
|
+
throw new Error(
|
|
52634
|
+
`Blockscout's public explorer for chain ${chain.chainId} refused the request (HTTP ${error.status}, bot protection). Set BLOCKSCOUT_API_KEY (free API key at dev.blockscout.com) to use the Blockscout PRO API.`
|
|
52635
|
+
);
|
|
52636
|
+
}
|
|
52637
|
+
throw error;
|
|
52638
|
+
});
|
|
52639
|
+
}
|
|
52640
|
+
var ownCoinGecko = null;
|
|
52641
|
+
async function resolvePrice(chain, address, token) {
|
|
52642
|
+
const blockscoutRate = parseFiniteNumber3(token.exchange_rate);
|
|
52643
|
+
if (blockscoutRate !== null && blockscoutRate > 0) {
|
|
52644
|
+
return { price: blockscoutRate, source: "blockscout_exchange_rate" };
|
|
52645
|
+
}
|
|
52646
|
+
try {
|
|
52647
|
+
if (!ownCoinGecko)
|
|
52648
|
+
ownCoinGecko = new Coingecko2(getCoinGeckoClientOptions());
|
|
52649
|
+
const result2 = await ownCoinGecko.onchain.simple.networks.tokenPrice.getAddresses(
|
|
52650
|
+
address,
|
|
52651
|
+
{ network: chain.gecko }
|
|
52652
|
+
);
|
|
52653
|
+
const prices = result2?.data?.attributes?.token_prices ?? {};
|
|
52654
|
+
const price = parseFiniteNumber3(
|
|
52655
|
+
prices[address.toLowerCase()] ?? prices[address]
|
|
52656
|
+
);
|
|
52657
|
+
if (price !== null && price > 0) {
|
|
52658
|
+
return { price, source: "coingecko_onchain_price" };
|
|
52659
|
+
}
|
|
52660
|
+
} catch {
|
|
52661
|
+
}
|
|
52662
|
+
return { price: null, source: null };
|
|
52663
|
+
}
|
|
52664
|
+
async function getTokenHolderDistribution(args, now = Date.now) {
|
|
52665
|
+
const { network, chain, address, maxPages, priceOverride } = parseArgs(args);
|
|
52666
|
+
const started = now();
|
|
52667
|
+
const token = await blockscoutGet(chain, `/tokens/${address}`) ?? {};
|
|
52668
|
+
if (token.type !== void 0 && token.type !== "ERC-20") {
|
|
52669
|
+
throw new SignalsInputError(
|
|
52670
|
+
`this address is a ${String(token.type)} token; holder tiers are computed for ERC-20 tokens only.`
|
|
52671
|
+
);
|
|
52672
|
+
}
|
|
52673
|
+
const decimals = parseFiniteNumber3(token.decimals);
|
|
52674
|
+
if (decimals === null) {
|
|
52675
|
+
throw new Error("Blockscout returned no decimals for this token");
|
|
52676
|
+
}
|
|
52677
|
+
const totalHolders = parseFiniteNumber3(token.holders_count ?? token.holders);
|
|
52678
|
+
const { price, source: priceSource } = priceOverride !== null ? { price: priceOverride, source: "caller" } : await resolvePrice(chain, address, token);
|
|
52679
|
+
const base = {
|
|
52680
|
+
provider: BLOCKSCOUT_PROVIDER,
|
|
52681
|
+
network,
|
|
52682
|
+
chain_id: chain.chainId,
|
|
52683
|
+
address,
|
|
52684
|
+
token: {
|
|
52685
|
+
name: token.name ?? null,
|
|
52686
|
+
symbol: token.symbol ?? null,
|
|
52687
|
+
decimals,
|
|
52688
|
+
total_supply: token.total_supply ?? null
|
|
52689
|
+
},
|
|
52690
|
+
total_holders: totalHolders,
|
|
52691
|
+
price_usd: price,
|
|
52692
|
+
price_source: priceSource
|
|
52693
|
+
};
|
|
52694
|
+
if (price === null) {
|
|
52695
|
+
return {
|
|
52696
|
+
...base,
|
|
52697
|
+
runtime_status: "degraded",
|
|
52698
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
52699
|
+
tiers: null,
|
|
52700
|
+
note: "No USD price was found for this token, so holders could not be tiered by value. total_holders counts every address with a positive balance, dust included. Pass price_usd to tier anyway."
|
|
52701
|
+
};
|
|
52702
|
+
}
|
|
52703
|
+
const rows = [];
|
|
52704
|
+
let pages = 0;
|
|
52705
|
+
let cursor = "";
|
|
52706
|
+
let stop = "end_of_holders";
|
|
52707
|
+
for (; ; ) {
|
|
52708
|
+
if (pages >= maxPages) {
|
|
52709
|
+
stop = "max_pages";
|
|
52710
|
+
break;
|
|
52711
|
+
}
|
|
52712
|
+
if (now() - started > TIME_BUDGET_MS) {
|
|
52713
|
+
stop = "time_budget";
|
|
52714
|
+
break;
|
|
52715
|
+
}
|
|
52716
|
+
const page = await blockscoutGet(
|
|
52717
|
+
chain,
|
|
52718
|
+
`/tokens/${address}/holders${cursor}`
|
|
52719
|
+
) ?? {};
|
|
52720
|
+
pages += 1;
|
|
52721
|
+
const items = Array.isArray(page.items) ? page.items : [];
|
|
52722
|
+
let belowFloor = false;
|
|
52723
|
+
for (const item of items) {
|
|
52724
|
+
const holder = item ?? {};
|
|
52725
|
+
const units = parseFiniteNumber3(holder.value);
|
|
52726
|
+
if (units === null) continue;
|
|
52727
|
+
const usd2 = units / 10 ** decimals * price;
|
|
52728
|
+
rows.push({ usd: usd2, is_contract: holder.address?.is_contract === true });
|
|
52729
|
+
if (usd2 < 1) {
|
|
52730
|
+
belowFloor = true;
|
|
52731
|
+
break;
|
|
52732
|
+
}
|
|
52733
|
+
}
|
|
52734
|
+
if (belowFloor) {
|
|
52735
|
+
stop = "below_smallest_tier";
|
|
52736
|
+
break;
|
|
52737
|
+
}
|
|
52738
|
+
const next = page.next_page_params;
|
|
52739
|
+
if (!next || typeof next !== "object" || items.length === 0) break;
|
|
52740
|
+
cursor = `?${new URLSearchParams(
|
|
52741
|
+
Object.entries(next).map(([k, v]) => [
|
|
52742
|
+
k,
|
|
52743
|
+
String(v)
|
|
52744
|
+
])
|
|
52745
|
+
).toString()}`;
|
|
52746
|
+
}
|
|
52747
|
+
const tally = tallyTiers(rows, totalHolders, stop);
|
|
52748
|
+
return {
|
|
52749
|
+
...base,
|
|
52750
|
+
runtime_status: "ok",
|
|
52751
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
52752
|
+
holders_scanned: rows.length,
|
|
52753
|
+
pages_fetched: pages,
|
|
52754
|
+
stopped_reason: stop,
|
|
52755
|
+
...tally,
|
|
52756
|
+
lower_bound: !tally.exhaustive,
|
|
52757
|
+
note: (tally.exhaustive ? "Every tier is exact." : `The walk stopped (${stop}) at a holder worth $${tally.smallest_scanned_usd?.toFixed(2)}; tiers marked exact are complete, lower_bound tiers are at least the count shown, and null tiers were not reached. Raise max_pages (up to ${MAX_PAGES}) to go deeper.`) + " Counts include contracts (pools, vesting, treasuries); each tier's contracts field says how many. USD values use price_usd at fetch time."
|
|
52758
|
+
};
|
|
52759
|
+
}
|
|
52760
|
+
function getAllBlockscoutEndpoints() {
|
|
52761
|
+
return [
|
|
52762
|
+
{
|
|
52763
|
+
metadata: {
|
|
52764
|
+
resource: "blockscout.tokens.holders",
|
|
52765
|
+
operation: "read",
|
|
52766
|
+
tags: ["blockscout", "tokens", "holders", "distribution", "onchain"]
|
|
52767
|
+
},
|
|
52768
|
+
tool: {
|
|
52769
|
+
name: "get_token_holder_distribution",
|
|
52770
|
+
description: `Count a token's holders by USD balance: total holders plus how many hold >$10k, $1k-$10k, $100-$1k, $1-$100, and under $1 (dust), from Blockscout's holder list. Answers "how many real (non-dust) holders does this token have?". Keyless on EVM chains: ${Object.keys(CHAINS).join(", ")}.
|
|
52771
|
+
|
|
52772
|
+
Args:
|
|
52773
|
+
- network (string, required): ${Object.keys(CHAINS).join(", ")}, or a chain id (1, 8453, 10, 42161, 137).
|
|
52774
|
+
- address (string, required): ERC-20 token contract address.
|
|
52775
|
+
- max_pages (integer, optional): holder pages of ${HOLDERS_PAGE_SIZE} to walk, 1-${MAX_PAGES} (default ${DEFAULT_MAX_PAGES}). The walk also stops after ${TIME_BUDGET_MS / 1e3} s, at the end of the list, or at the first holder under $1.
|
|
52776
|
+
- price_usd (number, optional): USD price to tier with. Default: Blockscout's exchange rate, then the GeckoTerminal on-chain price.
|
|
52777
|
+
|
|
52778
|
+
Returns:
|
|
52779
|
+
{ "provider": "Blockscout", "runtime_status", "fetched_at", "network", "chain_id", "address", "token": { "name", "symbol", "decimals", "total_supply" }, "total_holders", "price_usd", "price_source", "holders_scanned", "pages_fetched", "stopped_reason", "tiers": [{ "label", "min_usd", "max_usd", "count", "contracts", "exact", "lower_bound" }], "holders_100_usd_plus": { "count", "exact" }, "exhaustive", "lower_bound", "smallest_scanned_usd", "note" }. A tier with exact=false and lower_bound=true holds at least count holders; count null means the walk never reached it.
|
|
52780
|
+
|
|
52781
|
+
Examples:
|
|
52782
|
+
- { "network": "base", "address": "0x91dA780BC7f4B7Cf19ABE90411a2a296Ec5FF787" }
|
|
52783
|
+
- { "network": "eth", "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "max_pages": 5 }
|
|
52784
|
+
|
|
52785
|
+
Use when:
|
|
52786
|
+
- You need holder counts above a USD size, or how much of the holder count is dust.
|
|
52787
|
+
Don't use when:
|
|
52788
|
+
- You need the largest holders and their share of supply (use get_token_top_holders).`,
|
|
52789
|
+
inputSchema: {
|
|
52790
|
+
type: "object",
|
|
52791
|
+
properties: {
|
|
52792
|
+
network: {
|
|
52793
|
+
type: "string",
|
|
52794
|
+
description: `Chain: ${Object.keys(CHAINS).join(", ")}, or a chain id.`
|
|
52795
|
+
},
|
|
52796
|
+
address: {
|
|
52797
|
+
type: "string",
|
|
52798
|
+
description: "ERC-20 token contract address (0x...)."
|
|
52799
|
+
},
|
|
52800
|
+
max_pages: {
|
|
52801
|
+
type: "integer",
|
|
52802
|
+
minimum: 1,
|
|
52803
|
+
maximum: MAX_PAGES,
|
|
52804
|
+
default: DEFAULT_MAX_PAGES,
|
|
52805
|
+
description: `Holder pages of ${HOLDERS_PAGE_SIZE} to walk (1-${MAX_PAGES}).`
|
|
52806
|
+
},
|
|
52807
|
+
price_usd: {
|
|
52808
|
+
type: "number",
|
|
52809
|
+
exclusiveMinimum: 0,
|
|
52810
|
+
description: "USD price to tier with; default Blockscout's rate, then GeckoTerminal."
|
|
52811
|
+
}
|
|
52812
|
+
},
|
|
52813
|
+
required: ["network", "address"],
|
|
52814
|
+
additionalProperties: false
|
|
52815
|
+
},
|
|
52816
|
+
annotations: VOLATILE_READ_ANNOTATIONS
|
|
52817
|
+
},
|
|
52818
|
+
handler: async (_client, args) => {
|
|
52819
|
+
try {
|
|
52820
|
+
return asTextContentResult(await getTokenHolderDistribution(args));
|
|
52821
|
+
} catch (error) {
|
|
52822
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52823
|
+
return asTextContentResult(
|
|
52824
|
+
{
|
|
52825
|
+
error: message,
|
|
52826
|
+
runtime_status: error instanceof SignalsInputError ? "invalid_input" : classifyRuntimeStatusFromError(message),
|
|
52827
|
+
tool: "get_token_holder_distribution",
|
|
52828
|
+
provider: BLOCKSCOUT_PROVIDER
|
|
52829
|
+
},
|
|
52830
|
+
true
|
|
52831
|
+
);
|
|
52832
|
+
}
|
|
52833
|
+
}
|
|
52834
|
+
}
|
|
52835
|
+
];
|
|
52836
|
+
}
|
|
52837
|
+
|
|
52297
52838
|
// src/freeWriteEndpoints.ts
|
|
52298
52839
|
var FREE_WRITE_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
52299
52840
|
// Feedback is a bounded, rate-limited insert into Hive's own table. It
|
|
@@ -52863,7 +53404,7 @@ Don't use when:
|
|
|
52863
53404
|
}
|
|
52864
53405
|
|
|
52865
53406
|
// src/wrapper/index.ts
|
|
52866
|
-
import
|
|
53407
|
+
import Coingecko3 from "@coingecko/coingecko-typescript";
|
|
52867
53408
|
|
|
52868
53409
|
// src/utils/descriptionGenerator.ts
|
|
52869
53410
|
function needsEnrichment(description) {
|
|
@@ -53634,6 +54175,11 @@ var CHECK_TOKEN_SAFETY_OUTPUT_SCHEMA = {
|
|
|
53634
54175
|
additionalProperties: true,
|
|
53635
54176
|
description: "Raw provider risk datapoints; '1' means flagged. A missing key means the provider did not report that signal, not that it passed."
|
|
53636
54177
|
},
|
|
54178
|
+
summary: {
|
|
54179
|
+
type: "object",
|
|
54180
|
+
additionalProperties: true,
|
|
54181
|
+
description: "Concise scam signals: verdicts (plain-language list), lp (lock state and top LP holder), top_holders (largest holder and top-10 share, DEX pairs excluded), creator, owner. Percents are 0-100."
|
|
54182
|
+
},
|
|
53637
54183
|
holder_count: {},
|
|
53638
54184
|
token_name: { type: "string" },
|
|
53639
54185
|
note: { type: "string" }
|
|
@@ -54177,6 +54723,15 @@ var PROVIDER_CATALOG = [
|
|
|
54177
54723
|
alwaysAvailable: true,
|
|
54178
54724
|
matches: (_toolName, metadata100) => metadata100.tags.includes("blockchain-com")
|
|
54179
54725
|
},
|
|
54726
|
+
{
|
|
54727
|
+
id: "blockscout",
|
|
54728
|
+
name: "Blockscout",
|
|
54729
|
+
prefix: "blockscout_",
|
|
54730
|
+
type: "EVM token holder lists and counts from Blockscout explorers (keyless public API; BLOCKSCOUT_API_KEY switches to the PRO API)",
|
|
54731
|
+
clientStrategy: "internal",
|
|
54732
|
+
alwaysAvailable: true,
|
|
54733
|
+
matches: (_toolName, metadata100) => metadata100.tags.includes("blockscout")
|
|
54734
|
+
},
|
|
54180
54735
|
{
|
|
54181
54736
|
id: "fetch",
|
|
54182
54737
|
name: "Open Data Fetch",
|
|
@@ -54414,7 +54969,9 @@ var HELIUS_WALLET_PLAN = "because Helius answers 403 for it on Hive's current He
|
|
|
54414
54969
|
var DEFILLAMA_PRO = "because it needs a DeFiLlama Pro plan, which Hive does not have";
|
|
54415
54970
|
var DEFILLAMA_DERIVATIVES_REPLACEMENT = "Use defillama_get_open_interest, defillama_get_derivatives_aggregator_volumes, or get_derivatives_exchanges for derivatives activity.";
|
|
54416
54971
|
var ALCHEMY_PAYG = "because Alchemy offers it only on Pay As You Go and Enterprise plans, and Hive's key is on the Free tier";
|
|
54417
|
-
var ALCHEMY_BUNDLE_REPLACEMENT = "Use tenderly_simulate_bundle for multi-transaction simulation
|
|
54972
|
+
var ALCHEMY_BUNDLE_REPLACEMENT = "Use tenderly_simulate_bundle or tenderly_node_simulate_bundle for multi-transaction simulation.";
|
|
54973
|
+
var ALCHEMY_SIMULATION_SUNSET = 'because Alchemy shut down its Transaction Simulation API on 2026-09-30 and now answers "Unsupported method" on every plan';
|
|
54974
|
+
var ALCHEMY_SIMULATION_REPLACEMENT = "Use tenderly_simulate_transaction or tenderly_node_simulate_transaction; both return the execution trace, logs, and asset and balance changes without broadcasting.";
|
|
54418
54975
|
var CCXT_PRIVATE = "because every exchange that serves it requires an authenticated exchange account";
|
|
54419
54976
|
var CCXT_BORROW_REPLACEMENT = "Use ccxt_fetch_borrow_rate_history or ccxt_fetch_borrow_rate_histories (public) and read the latest point.";
|
|
54420
54977
|
var UNSERVABLE_TOOLS = {
|
|
@@ -54498,6 +55055,21 @@ var UNSERVABLE_TOOLS = {
|
|
|
54498
55055
|
reason: CCXT_PRIVATE,
|
|
54499
55056
|
replacement: CCXT_BORROW_REPLACEMENT
|
|
54500
55057
|
},
|
|
55058
|
+
alchemy_simulate_asset_changes: {
|
|
55059
|
+
reason: ALCHEMY_SIMULATION_SUNSET,
|
|
55060
|
+
replacement: ALCHEMY_SIMULATION_REPLACEMENT,
|
|
55061
|
+
date: "2026-10-09"
|
|
55062
|
+
},
|
|
55063
|
+
alchemy_simulate_execution: {
|
|
55064
|
+
reason: ALCHEMY_SIMULATION_SUNSET,
|
|
55065
|
+
replacement: ALCHEMY_SIMULATION_REPLACEMENT,
|
|
55066
|
+
date: "2026-10-09"
|
|
55067
|
+
},
|
|
55068
|
+
alchemy_is_holder_of_contract: {
|
|
55069
|
+
reason: 'because Alchemy deprecated the isHolderOfContract endpoint, which now answers 404 "This API endpoint has been deprecated"',
|
|
55070
|
+
replacement: "Use alchemy_get_nfts_for_owner for the wallet and look for the contract, or alchemy_get_owners_for_contract and look for the wallet.",
|
|
55071
|
+
date: "2026-10-09"
|
|
55072
|
+
},
|
|
54501
55073
|
goplus_simulate_solana_transaction: {
|
|
54502
55074
|
reason: 'because GoPlus answers "system error" to every Solana pre-execution request, including a fresh valid transfer',
|
|
54503
55075
|
replacement: "Use get_solana_token_security for the tokens the transaction touches; Hive has no other Solana pre-signing simulation."
|
|
@@ -54510,7 +55082,7 @@ function getRetiredTool(toolName) {
|
|
|
54510
55082
|
if (isUnservableTool(toolName)) {
|
|
54511
55083
|
const entry2 = UNSERVABLE_TOOLS[toolName];
|
|
54512
55084
|
return {
|
|
54513
|
-
date: UNSERVABLE_TOOLS_DATE,
|
|
55085
|
+
date: entry2.date ?? UNSERVABLE_TOOLS_DATE,
|
|
54514
55086
|
cause: entry2.reason,
|
|
54515
55087
|
replacement: entry2.replacement
|
|
54516
55088
|
};
|
|
@@ -54687,7 +55259,40 @@ function boundedJsonTruncationStub(originalLength, note, characterLimit) {
|
|
|
54687
55259
|
];
|
|
54688
55260
|
return candidates.find((candidate) => candidate.length <= characterLimit) ?? "";
|
|
54689
55261
|
}
|
|
54690
|
-
function
|
|
55262
|
+
function inputSchemaParamNames(schema) {
|
|
55263
|
+
const properties = schema?.properties;
|
|
55264
|
+
return properties && typeof properties === "object" ? Object.keys(properties) : [];
|
|
55265
|
+
}
|
|
55266
|
+
function truncationRecallHint(params = [], nextOffset) {
|
|
55267
|
+
const names = new Set(params);
|
|
55268
|
+
if (names.has("offset")) {
|
|
55269
|
+
return `Re-call with offset=${nextOffset ?? "_pagination.next_offset"} to continue, or add filters.`;
|
|
55270
|
+
}
|
|
55271
|
+
const sizeParam = ["limit", "per_page", "page_size", "count"].find(
|
|
55272
|
+
(name) => names.has(name)
|
|
55273
|
+
);
|
|
55274
|
+
return sizeParam ? `This tool has no offset parameter: a smaller ${sizeParam} returns an untruncated page; narrower filters reach the remaining rows.` : "This tool has no paging parameter: narrow the request with its filters to see the rest.";
|
|
55275
|
+
}
|
|
55276
|
+
function truncationPagination(kept, original, context) {
|
|
55277
|
+
const params = [...context.params ?? []];
|
|
55278
|
+
const head = `Response truncated from ${original} to ${kept} items to fit the response budget.`;
|
|
55279
|
+
const base = {
|
|
55280
|
+
count: kept,
|
|
55281
|
+
total_count: original,
|
|
55282
|
+
has_more: kept < original
|
|
55283
|
+
};
|
|
55284
|
+
if (!params.includes("offset")) {
|
|
55285
|
+
return { ...base, note: `${head} ${truncationRecallHint(params)}` };
|
|
55286
|
+
}
|
|
55287
|
+
const start = typeof context.offset === "number" && Number.isFinite(context.offset) ? Math.max(0, Math.floor(context.offset)) : 0;
|
|
55288
|
+
return {
|
|
55289
|
+
...base,
|
|
55290
|
+
offset: start,
|
|
55291
|
+
next_offset: start + kept,
|
|
55292
|
+
note: `${head} ${truncationRecallHint(params, start + kept)}`
|
|
55293
|
+
};
|
|
55294
|
+
}
|
|
55295
|
+
function smartTruncate(text, characterLimit = CHARACTER_LIMIT, pagination = {}) {
|
|
54691
55296
|
if (text.length <= characterLimit) return text;
|
|
54692
55297
|
const originalLength = text.length;
|
|
54693
55298
|
const head = text.trimStart();
|
|
@@ -54748,14 +55353,11 @@ function smartTruncate(text, characterLimit = CHARACTER_LIMIT) {
|
|
|
54748
55353
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
54749
55354
|
const obj = parsed;
|
|
54750
55355
|
if (pagInfo && pagInfo.kept < pagInfo.original) {
|
|
54751
|
-
obj._pagination =
|
|
54752
|
-
|
|
54753
|
-
|
|
54754
|
-
|
|
54755
|
-
|
|
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
|
-
};
|
|
55356
|
+
obj._pagination = truncationPagination(
|
|
55357
|
+
pagInfo.kept,
|
|
55358
|
+
pagInfo.original,
|
|
55359
|
+
pagination
|
|
55360
|
+
);
|
|
54759
55361
|
}
|
|
54760
55362
|
if (dropped.length > 0) {
|
|
54761
55363
|
obj._dropped_fields = dropped;
|
|
@@ -54872,14 +55474,15 @@ function stripProviderPrefix(toolName) {
|
|
|
54872
55474
|
var fallbackCoinGeckoClient = null;
|
|
54873
55475
|
function getFallbackCoinGeckoClient() {
|
|
54874
55476
|
if (!fallbackCoinGeckoClient) {
|
|
54875
|
-
fallbackCoinGeckoClient = new
|
|
55477
|
+
fallbackCoinGeckoClient = new Coingecko3(getCoinGeckoClientOptions());
|
|
54876
55478
|
}
|
|
54877
55479
|
return fallbackCoinGeckoClient;
|
|
54878
55480
|
}
|
|
54879
55481
|
function isKeylessCoinGeckoClient(client2) {
|
|
54880
|
-
return client2 instanceof
|
|
55482
|
+
return client2 instanceof Coingecko3 && !client2.proAPIKey && !client2.demoAPIKey;
|
|
54881
55483
|
}
|
|
54882
|
-
function createSafeHandler(handler96, metadata100, toolName) {
|
|
55484
|
+
function createSafeHandler(handler96, metadata100, toolName, inputSchema) {
|
|
55485
|
+
const schemaParams = inputSchemaParamNames(inputSchema);
|
|
54883
55486
|
const credentialRequirement = getCredentialRequirementForTool(
|
|
54884
55487
|
toolName,
|
|
54885
55488
|
metadata100
|
|
@@ -54975,7 +55578,15 @@ function createSafeHandler(handler96, metadata100, toolName) {
|
|
|
54975
55578
|
withExecMarker(
|
|
54976
55579
|
{
|
|
54977
55580
|
...result2,
|
|
54978
|
-
content: [
|
|
55581
|
+
content: [
|
|
55582
|
+
{
|
|
55583
|
+
type: "text",
|
|
55584
|
+
text: smartTruncate(text, CHARACTER_LIMIT, {
|
|
55585
|
+
params: schemaParams,
|
|
55586
|
+
offset: safeArgs.offset
|
|
55587
|
+
})
|
|
55588
|
+
}
|
|
55589
|
+
]
|
|
54979
55590
|
},
|
|
54980
55591
|
{
|
|
54981
55592
|
truncated: true,
|
|
@@ -55138,6 +55749,7 @@ function getAllWrappedEndpoints() {
|
|
|
55138
55749
|
...getAllArchiveEndpoints(),
|
|
55139
55750
|
...getAllPolymarketEndpoints(),
|
|
55140
55751
|
...getAllSignalsEndpoints(),
|
|
55752
|
+
...getAllBlockscoutEndpoints(),
|
|
55141
55753
|
...getAllFetchEndpoints()
|
|
55142
55754
|
];
|
|
55143
55755
|
const allEndpointsJSON = wrapper;
|
|
@@ -55193,7 +55805,8 @@ function getAllWrappedEndpoints() {
|
|
|
55193
55805
|
handler: createSafeHandler(
|
|
55194
55806
|
endpoint.handler,
|
|
55195
55807
|
endpoint.metadata,
|
|
55196
|
-
toolInstanse.name
|
|
55808
|
+
toolInstanse.name,
|
|
55809
|
+
toolInstanse.inputSchema
|
|
55197
55810
|
),
|
|
55198
55811
|
metadata: endpoint.metadata,
|
|
55199
55812
|
tool: toolInstanse
|
|
@@ -55237,7 +55850,8 @@ function getAllWrappedEndpoints() {
|
|
|
55237
55850
|
handler: createSafeHandler(
|
|
55238
55851
|
endpoint.handler,
|
|
55239
55852
|
endpoint.metadata,
|
|
55240
|
-
toolInstanse.name
|
|
55853
|
+
toolInstanse.name,
|
|
55854
|
+
toolInstanse.inputSchema
|
|
55241
55855
|
),
|
|
55242
55856
|
metadata: endpoint.metadata,
|
|
55243
55857
|
tool: toolInstanse
|
|
@@ -55594,6 +56208,7 @@ var ToolRegistry = [
|
|
|
55594
56208
|
"get_tokens_by_address",
|
|
55595
56209
|
"get_token_info",
|
|
55596
56210
|
"get_token_top_holders",
|
|
56211
|
+
"get_token_holder_distribution",
|
|
55597
56212
|
"get_token_holders_chart",
|
|
55598
56213
|
"get_multi_tokens",
|
|
55599
56214
|
"get_recently_updated_tokens",
|
|
@@ -55696,7 +56311,6 @@ var ToolRegistry = [
|
|
|
55696
56311
|
"alchemy_get_floor_price",
|
|
55697
56312
|
"alchemy_get_contracts_for_owner",
|
|
55698
56313
|
"alchemy_is_spam_contract",
|
|
55699
|
-
"alchemy_is_holder_of_contract",
|
|
55700
56314
|
"alchemy_solana_get_asset",
|
|
55701
56315
|
"alchemy_solana_get_asset_proof",
|
|
55702
56316
|
"alchemy_solana_get_asset_proofs",
|
|
@@ -55798,9 +56412,7 @@ var ToolRegistry = [
|
|
|
55798
56412
|
"tenderly_get_contract_abi",
|
|
55799
56413
|
"tenderly_get_storage_changes",
|
|
55800
56414
|
"tenderly_get_transactions_range",
|
|
55801
|
-
"alchemy_get_token_allowance"
|
|
55802
|
-
"alchemy_simulate_asset_changes",
|
|
55803
|
-
"alchemy_simulate_execution"
|
|
56415
|
+
"alchemy_get_token_allowance"
|
|
55804
56416
|
]
|
|
55805
56417
|
},
|
|
55806
56418
|
{
|
|
@@ -55973,7 +56585,7 @@ import {
|
|
|
55973
56585
|
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
55974
56586
|
import { dirname, resolve as resolvePath } from "path";
|
|
55975
56587
|
import { fileURLToPath } from "url";
|
|
55976
|
-
import
|
|
56588
|
+
import Coingecko5 from "@coingecko/coingecko-typescript";
|
|
55977
56589
|
|
|
55978
56590
|
// src/tools/dynamic-tools.ts
|
|
55979
56591
|
import { z as z4 } from "zod";
|
|
@@ -56289,20 +56901,51 @@ function isUnmarkedErrorResponse(result2) {
|
|
|
56289
56901
|
}
|
|
56290
56902
|
return false;
|
|
56291
56903
|
}
|
|
56904
|
+
var PROVIDER_SIDE_ERROR_COPY = {
|
|
56905
|
+
plan_required: {
|
|
56906
|
+
message: "The upstream provider declined this call: the endpoint is outside this deployment's provider plan or quota (runtime_status plan_required). The arguments were not the problem.",
|
|
56907
|
+
suggestion: "Use search_tools to pick another tool for the same data. Retrying unchanged will fail again."
|
|
56908
|
+
},
|
|
56909
|
+
rate_limited: {
|
|
56910
|
+
message: "The upstream provider rate limit was hit (runtime_status rate_limited).",
|
|
56911
|
+
suggestion: "Wait about 60 seconds before one retry, or narrow the request with this tool's filters."
|
|
56912
|
+
},
|
|
56913
|
+
missing_key: {
|
|
56914
|
+
suggestion: "This provider has no working credential on this deployment. Use search_tools to pick another tool for the same data; self-hosted deployments can configure the provider's API key."
|
|
56915
|
+
}
|
|
56916
|
+
};
|
|
56917
|
+
var VENDOR_ACCOUNT_URL = /https?:\/\/[^\s"')\]]*(?:billing|pricing|plan|upgrade|admin|dashboard|account|subscri|checkout)[^\s"')\]]*/gi;
|
|
56918
|
+
function sanitizeProviderError(message, suggestion) {
|
|
56919
|
+
const copy = PROVIDER_SIDE_ERROR_COPY[classifyRuntimeStatusFromError(message)];
|
|
56920
|
+
return {
|
|
56921
|
+
message: (copy?.message ?? message).replace(
|
|
56922
|
+
VENDOR_ACCOUNT_URL,
|
|
56923
|
+
"[link removed]"
|
|
56924
|
+
),
|
|
56925
|
+
suggestion: copy?.suggestion ?? suggestion,
|
|
56926
|
+
providerSide: Boolean(copy)
|
|
56927
|
+
};
|
|
56928
|
+
}
|
|
56929
|
+
function collapseErrorMarkers(text) {
|
|
56930
|
+
return text.replace(/(?:\*\*Error:\*\*\s*){2,}/g, "**Error:** ");
|
|
56931
|
+
}
|
|
56292
56932
|
function normalizeErrorText(text, toolName, args) {
|
|
56293
56933
|
let errorMsg = "";
|
|
56294
56934
|
let code = "";
|
|
56295
56935
|
let suggestion = "";
|
|
56296
|
-
let alternatives =
|
|
56936
|
+
let alternatives = [];
|
|
56297
56937
|
let documentation = "";
|
|
56298
56938
|
try {
|
|
56299
56939
|
const parsed = JSON.parse(text);
|
|
56300
56940
|
if (parsed && typeof parsed === "object" && typeof parsed.error === "string") {
|
|
56301
56941
|
errorMsg = parsed.error;
|
|
56942
|
+
if (typeof parsed.message === "string" && parsed.message.trim() && !errorMsg.includes(parsed.message)) {
|
|
56943
|
+
errorMsg += `: ${parsed.message.replace(/https?:\/\/\S+/g, "[url]")}`;
|
|
56944
|
+
}
|
|
56302
56945
|
code = typeof parsed.code === "string" ? parsed.code : "";
|
|
56303
56946
|
suggestion = parsed.suggestion || "";
|
|
56304
56947
|
if (Array.isArray(parsed.alternatives) && parsed.alternatives.length > 0) {
|
|
56305
|
-
alternatives = parsed.alternatives.
|
|
56948
|
+
alternatives = parsed.alternatives.map(String);
|
|
56306
56949
|
}
|
|
56307
56950
|
documentation = parsed.documentation || "";
|
|
56308
56951
|
}
|
|
@@ -56310,7 +56953,18 @@ function normalizeErrorText(text, toolName, args) {
|
|
|
56310
56953
|
}
|
|
56311
56954
|
if (errorMsg) {
|
|
56312
56955
|
const bareErrorMsg = errorMsg.replace(/^(?:\s*\*\*Error:\*\*\s*)+/, "");
|
|
56313
|
-
|
|
56956
|
+
if (bareErrorMsg.includes("\n\n**Code:**")) {
|
|
56957
|
+
return collapseErrorMarkers(`**Error:** ${bareErrorMsg}`);
|
|
56958
|
+
}
|
|
56959
|
+
const safe = sanitizeProviderError(bareErrorMsg, suggestion);
|
|
56960
|
+
suggestion = safe.suggestion;
|
|
56961
|
+
const prefix = toolName?.split("_")[0];
|
|
56962
|
+
if (safe.providerSide && prefix) {
|
|
56963
|
+
alternatives = alternatives.filter(
|
|
56964
|
+
(name) => !name.startsWith(`${prefix}_`)
|
|
56965
|
+
);
|
|
56966
|
+
}
|
|
56967
|
+
let normalized = `**Error:** ${safe.message}`;
|
|
56314
56968
|
normalized += `
|
|
56315
56969
|
|
|
56316
56970
|
**Code:** ${code || "API_ERROR"}`;
|
|
@@ -56326,15 +56980,29 @@ function normalizeErrorText(text, toolName, args) {
|
|
|
56326
56980
|
|
|
56327
56981
|
**Params:** ${truncated}`;
|
|
56328
56982
|
}
|
|
56329
|
-
if (alternatives
|
|
56983
|
+
if (alternatives.length > 0) {
|
|
56984
|
+
normalized += `
|
|
56330
56985
|
|
|
56331
|
-
**Alternatives:** ${alternatives}`;
|
|
56986
|
+
**Alternatives:** ${alternatives.join(", ")}`;
|
|
56987
|
+
}
|
|
56332
56988
|
if (documentation) normalized += `
|
|
56333
56989
|
|
|
56334
56990
|
**Documentation:** ${documentation}`;
|
|
56335
56991
|
return normalized;
|
|
56336
56992
|
}
|
|
56337
|
-
let result2 = text;
|
|
56993
|
+
let result2 = collapseErrorMarkers(text);
|
|
56994
|
+
const head = /^\*\*Error:\*\* (.*?)(?=\n\n\*\*|$)/s.exec(result2);
|
|
56995
|
+
if (head) {
|
|
56996
|
+
const safe = sanitizeProviderError(head[1], "");
|
|
56997
|
+
result2 = result2.replace(head[0], `**Error:** ${safe.message}`);
|
|
56998
|
+
if (safe.suggestion) {
|
|
56999
|
+
result2 = result2.replace(
|
|
57000
|
+
/\*\*Suggestion:\*\* [^\n]*/,
|
|
57001
|
+
`**Suggestion:** ${safe.suggestion}`
|
|
57002
|
+
);
|
|
57003
|
+
}
|
|
57004
|
+
}
|
|
57005
|
+
result2 = result2.replace(VENDOR_ACCOUNT_URL, "[link removed]");
|
|
56338
57006
|
if (toolName && !result2.includes("**Tool:**")) {
|
|
56339
57007
|
result2 += `
|
|
56340
57008
|
|
|
@@ -57039,7 +57707,7 @@ function agentErrorCopy(status, context = {}) {
|
|
|
57039
57707
|
case "plan_required":
|
|
57040
57708
|
return {
|
|
57041
57709
|
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
|
|
57710
|
+
next_action: "Use search_tools to pick an alternative provider tool for the same data. Retrying unchanged will fail again."
|
|
57043
57711
|
};
|
|
57044
57712
|
case "rate_limited":
|
|
57045
57713
|
return {
|
|
@@ -57613,17 +58281,17 @@ function fitPayloadWithinReceiptBudget(value, hive, isError = false) {
|
|
|
57613
58281
|
const payloadBudget = Math.max(256, CHARACTER_LIMIT - receiptLength + 1);
|
|
57614
58282
|
const truncated = smartTruncate(JSON.stringify(payload), payloadBudget);
|
|
57615
58283
|
const parsed = parseToolText(truncated);
|
|
57616
|
-
let
|
|
57617
|
-
hive.result_digest = digestHiveCanonicalJson(
|
|
57618
|
-
if (JSON.stringify({ ...
|
|
57619
|
-
|
|
58284
|
+
let boundedPayload2 = toHiveResultPayload(parsed);
|
|
58285
|
+
hive.result_digest = digestHiveCanonicalJson(boundedPayload2);
|
|
58286
|
+
if (JSON.stringify({ ...boundedPayload2, _hive: hive }).length > CHARACTER_LIMIT) {
|
|
58287
|
+
boundedPayload2 = {
|
|
57620
58288
|
_truncated: true,
|
|
57621
58289
|
_original_length: JSON.stringify(payload).length,
|
|
57622
58290
|
_note: "Response omitted because the provider payload plus its execution receipt exceeded the response limit. Re-call with narrower parameters or filters."
|
|
57623
58291
|
};
|
|
57624
|
-
hive.result_digest = digestHiveCanonicalJson(
|
|
58292
|
+
hive.result_digest = digestHiveCanonicalJson(boundedPayload2);
|
|
57625
58293
|
}
|
|
57626
|
-
return
|
|
58294
|
+
return boundedPayload2;
|
|
57627
58295
|
}
|
|
57628
58296
|
function createHiveExecutionMetadata(endpoint, durationMs, runtimeStatus, source, warnings = [], exec, integrity = {}, credit = {}) {
|
|
57629
58297
|
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -58162,6 +58830,17 @@ function executionGateFailureResult(endpoint, gate, durationMs, args, credit = {
|
|
|
58162
58830
|
};
|
|
58163
58831
|
}
|
|
58164
58832
|
async function executeEndpoint(endpoint, args, context) {
|
|
58833
|
+
const result2 = await executeEndpointUnchecked(endpoint, args, context);
|
|
58834
|
+
if (result2.mcpResult.isError && endpoint.tool.outputSchema && result2.mcpResult.structuredContent !== void 0 && validateJsonSchema(
|
|
58835
|
+
result2.mcpResult.structuredContent,
|
|
58836
|
+
endpoint.tool.outputSchema
|
|
58837
|
+
).length > 0) {
|
|
58838
|
+
const { structuredContent: _omitted, ...textOnly } = result2.mcpResult;
|
|
58839
|
+
return { ...result2, mcpResult: textOnly };
|
|
58840
|
+
}
|
|
58841
|
+
return result2;
|
|
58842
|
+
}
|
|
58843
|
+
async function executeEndpointUnchecked(endpoint, args, context) {
|
|
58165
58844
|
const start = context.startMs ?? Date.now();
|
|
58166
58845
|
const validationErrors = validateEndpointArgs(endpoint, args);
|
|
58167
58846
|
if (validationErrors.length > 0) {
|
|
@@ -58277,15 +58956,22 @@ async function executeEndpoint(endpoint, args, context) {
|
|
|
58277
58956
|
const guardLimit = getAnnotatedMaxResultSizeChars(endpoint.tool) ?? DEFAULT_RESULT_SIZE_GUARD_CHARS;
|
|
58278
58957
|
const successText = getTextContent(normalized);
|
|
58279
58958
|
if (successText && successText.length > guardLimit) {
|
|
58959
|
+
const guardParams = inputSchemaParamNames(endpoint.tool.inputSchema);
|
|
58280
58960
|
bounded = {
|
|
58281
58961
|
...normalized,
|
|
58282
58962
|
content: [
|
|
58283
|
-
{
|
|
58963
|
+
{
|
|
58964
|
+
type: "text",
|
|
58965
|
+
text: smartTruncate(successText, guardLimit, {
|
|
58966
|
+
params: guardParams,
|
|
58967
|
+
offset: args?.offset
|
|
58968
|
+
})
|
|
58969
|
+
}
|
|
58284
58970
|
]
|
|
58285
58971
|
};
|
|
58286
58972
|
execForHive = { ...exec, truncated: true };
|
|
58287
58973
|
guardWarnings.push(
|
|
58288
|
-
`Response exceeded ${guardLimit} characters and was truncated for model-context safety.
|
|
58974
|
+
`Response exceeded ${guardLimit} characters and was truncated for model-context safety. ${truncationRecallHint(guardParams)}`
|
|
58289
58975
|
);
|
|
58290
58976
|
}
|
|
58291
58977
|
}
|
|
@@ -58498,7 +59184,7 @@ var CHAIN_ID_NAMES = {};
|
|
|
58498
59184
|
for (const [name, id] of Object.entries(GOPLUS_CHAIN_IDS)) {
|
|
58499
59185
|
if (!(id in CHAIN_ID_NAMES)) CHAIN_ID_NAMES[id] = name;
|
|
58500
59186
|
}
|
|
58501
|
-
var
|
|
59187
|
+
var CHAIN_ALIASES2 = [
|
|
58502
59188
|
"chain",
|
|
58503
59189
|
"network",
|
|
58504
59190
|
"chain_id",
|
|
@@ -58535,7 +59221,7 @@ var HERO_ARG_ALIASES = {
|
|
|
58535
59221
|
"name"
|
|
58536
59222
|
],
|
|
58537
59223
|
address: CONTRACT_ADDRESS_ALIASES,
|
|
58538
|
-
chain:
|
|
59224
|
+
chain: CHAIN_ALIASES2,
|
|
58539
59225
|
vs_currency: [
|
|
58540
59226
|
"vs_currency",
|
|
58541
59227
|
"vsCurrency",
|
|
@@ -58548,7 +59234,7 @@ var HERO_ARG_ALIASES = {
|
|
|
58548
59234
|
},
|
|
58549
59235
|
check_token_safety: {
|
|
58550
59236
|
address: [...CONTRACT_ADDRESS_ALIASES, "token"],
|
|
58551
|
-
chain:
|
|
59237
|
+
chain: CHAIN_ALIASES2,
|
|
58552
59238
|
response_format: RESPONSE_FORMAT_ALIASES
|
|
58553
59239
|
},
|
|
58554
59240
|
get_wallet_portfolio: {
|
|
@@ -58561,7 +59247,8 @@ var HERO_ARG_ALIASES = {
|
|
|
58561
59247
|
"account_address",
|
|
58562
59248
|
"owner"
|
|
58563
59249
|
],
|
|
58564
|
-
chain:
|
|
59250
|
+
chain: CHAIN_ALIASES2,
|
|
59251
|
+
include_spam: ["include_spam", "includeSpam", "show_spam"],
|
|
58565
59252
|
response_format: RESPONSE_FORMAT_ALIASES
|
|
58566
59253
|
}
|
|
58567
59254
|
};
|
|
@@ -58848,9 +59535,10 @@ function heroTools(endpoints, options = {}) {
|
|
|
58848
59535
|
address,
|
|
58849
59536
|
chain: chainLabel,
|
|
58850
59537
|
risk_flags: flags,
|
|
59538
|
+
...isRecord3(entry2) ? { summary: summarizeTokenSafety(entry2) } : {},
|
|
58851
59539
|
...isRecord3(entry2) && entry2.holder_count !== void 0 ? { holder_count: entry2.holder_count } : {},
|
|
58852
59540
|
...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."
|
|
59541
|
+
note: "risk_flags are raw GoPlus datapoints; treat '1' as flagged. summary.verdicts restates the scam signals in plain language; an empty list means none fired, not that the token is safe. Use response_format=detailed for the full report."
|
|
58854
59542
|
};
|
|
58855
59543
|
};
|
|
58856
59544
|
const checkTokenSafety = {
|
|
@@ -58862,7 +59550,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
58862
59550
|
tool: {
|
|
58863
59551
|
name: "check_token_safety",
|
|
58864
59552
|
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.",
|
|
59553
|
+
description: "Token security screen for a contract address: honeypot, taxes, ownership, and related GoPlus risk datapoints, plus a summary of LP lock status, top-holder concentration, and creator/owner balances with plain-language verdicts. EVM chains use chain + address; Solana addresses are detected automatically.",
|
|
58866
59554
|
inputSchema: zodToInputSchema(tokenSafetySchema),
|
|
58867
59555
|
outputSchema: heroOutputSchema("check_token_safety"),
|
|
58868
59556
|
annotations: VOLATILE_READ_ANNOTATIONS
|
|
@@ -58906,9 +59594,13 @@ function heroTools(endpoints, options = {}) {
|
|
|
58906
59594
|
chain: z2.string().trim().max(40).optional().describe(
|
|
58907
59595
|
'Chain name, for example "eth", "base", "arbitrum", or "solana". Defaults to eth for 0x addresses; Solana is auto-detected.'
|
|
58908
59596
|
),
|
|
59597
|
+
include_spam: z2.boolean().optional().describe(
|
|
59598
|
+
"Show likely spam airdrops (tokens with a URL or claim lure in the name, and on EVM chains tokens with no market price). Default false; the response says how many were hidden."
|
|
59599
|
+
),
|
|
58909
59600
|
response_format: RESPONSE_FORMAT
|
|
58910
59601
|
});
|
|
58911
59602
|
const MAX_CONCISE_TOKENS = 15;
|
|
59603
|
+
const SPAM_LURE = /(https?:\/\/|www\.|t\.me\/|\.(com|xyz|io|org|net|app|site|top|lol|info|claim|gift)\b|\bclaim|\bvisit\b|airdrop|\breward)/i;
|
|
58912
59604
|
const NATIVE_SYMBOLS = {
|
|
58913
59605
|
eth: "ETH",
|
|
58914
59606
|
ethereum: "ETH",
|
|
@@ -58948,10 +59640,10 @@ function heroTools(endpoints, options = {}) {
|
|
|
58948
59640
|
};
|
|
58949
59641
|
const usdFromPrices = (token, balance) => {
|
|
58950
59642
|
if (!Array.isArray(token.tokenPrices)) return null;
|
|
58951
|
-
const
|
|
59643
|
+
const usd2 = token.tokenPrices.find(
|
|
58952
59644
|
(price) => isRecord3(price) && price.currency === "usd"
|
|
58953
59645
|
);
|
|
58954
|
-
const rate =
|
|
59646
|
+
const rate = usd2 && isRecord3(usd2) ? Number(usd2.value) : NaN;
|
|
58955
59647
|
const quantity = balance == null ? NaN : Number(balance);
|
|
58956
59648
|
const value = rate * quantity;
|
|
58957
59649
|
if (!Number.isFinite(value)) return null;
|
|
@@ -58968,7 +59660,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
58968
59660
|
}
|
|
58969
59661
|
return [];
|
|
58970
59662
|
};
|
|
58971
|
-
const trimWalletPortfolio = (address, chainLabel) => (payload) => {
|
|
59663
|
+
const trimWalletPortfolio = (address, chainLabel, includeSpam = false, priced = false) => (payload) => {
|
|
58972
59664
|
const walletLower = address.trim().toLowerCase();
|
|
58973
59665
|
const tokens = firstArray(payload, ["tokens", "items", "balances"]);
|
|
58974
59666
|
const summarized = tokens.map((token) => {
|
|
@@ -58991,18 +59683,28 @@ function heroTools(endpoints, options = {}) {
|
|
|
58991
59683
|
usd_value: token.totalValueUsd ?? token.valueUsd ?? token.usd_value ?? (typeof token.totalValueUsdCents === "number" ? token.totalValueUsdCents / 100 : usdFromPrices(token, balance))
|
|
58992
59684
|
};
|
|
58993
59685
|
});
|
|
58994
|
-
const
|
|
59686
|
+
const nonZero = summarized.filter(
|
|
58995
59687
|
(token) => !isRecord3(token) || token.balance !== "0"
|
|
58996
59688
|
);
|
|
59689
|
+
const isSpam = (token) => isRecord3(token) && token.address != null && (SPAM_LURE.test(`${token.name ?? ""} ${token.symbol ?? ""}`) || priced && token.usd_value == null);
|
|
59690
|
+
const held = includeSpam ? nonZero : nonZero.filter((t) => !isSpam(t));
|
|
59691
|
+
const hidden = nonZero.length - held.length;
|
|
58997
59692
|
return {
|
|
58998
59693
|
address,
|
|
58999
59694
|
chain: chainLabel,
|
|
59000
59695
|
token_count: held.length,
|
|
59696
|
+
...hidden > 0 ? {
|
|
59697
|
+
spam_hidden: {
|
|
59698
|
+
count: hidden,
|
|
59699
|
+
reason: priced ? "no market price, or a URL/claim lure in the name" : "a URL/claim lure in the name",
|
|
59700
|
+
note: "Likely spam airdrops; pass include_spam=true to show them."
|
|
59701
|
+
}
|
|
59702
|
+
} : {},
|
|
59001
59703
|
tokens: held.slice(0, MAX_CONCISE_TOKENS),
|
|
59002
59704
|
...held.length > MAX_CONCISE_TOKENS ? {
|
|
59003
59705
|
note: `Showing the first ${MAX_CONCISE_TOKENS} of ${held.length} held tokens (zero balances omitted). Use response_format=detailed for the full provider payload.`
|
|
59004
|
-
} : summarized.length >
|
|
59005
|
-
note: `${summarized.length -
|
|
59706
|
+
} : summarized.length > nonZero.length ? {
|
|
59707
|
+
note: `${summarized.length - nonZero.length} zero-balance tokens omitted. Use response_format=detailed for the full provider payload.`
|
|
59006
59708
|
} : {}
|
|
59007
59709
|
};
|
|
59008
59710
|
};
|
|
@@ -59015,7 +59717,7 @@ function heroTools(endpoints, options = {}) {
|
|
|
59015
59717
|
tool: {
|
|
59016
59718
|
name: "get_wallet_portfolio",
|
|
59017
59719
|
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.",
|
|
59720
|
+
description: "Token holdings for a wallet with balances and USD values where available. Likely spam airdrops are hidden and counted (include_spam=true shows them). EVM chains route through Alchemy; Solana wallets are detected automatically and route through Helius.",
|
|
59019
59721
|
inputSchema: zodToInputSchema(walletPortfolioSchema),
|
|
59020
59722
|
outputSchema: heroOutputSchema("get_wallet_portfolio"),
|
|
59021
59723
|
annotations: VOLATILE_READ_ANNOTATIONS
|
|
@@ -59031,14 +59733,18 @@ function heroTools(endpoints, options = {}) {
|
|
|
59031
59733
|
`Invalid arguments: ${formatSchemaIssues(parsed.error)}`
|
|
59032
59734
|
);
|
|
59033
59735
|
}
|
|
59034
|
-
const { address, chain } = parsed.data;
|
|
59736
|
+
const { address, chain, include_spam: includeSpam } = parsed.data;
|
|
59035
59737
|
const format = heroFormat(parsed.data.response_format);
|
|
59036
59738
|
if (looksLikeNameService(address)) return nameServiceError(address);
|
|
59037
59739
|
if (isSolanaChain(chain, address)) {
|
|
59038
59740
|
const result3 = await executeUnderlying("helius_get_wallet_balances", {
|
|
59039
59741
|
wallet: address
|
|
59040
59742
|
}, "get_wallet_portfolio");
|
|
59041
|
-
return respond(
|
|
59743
|
+
return respond(
|
|
59744
|
+
result3,
|
|
59745
|
+
format,
|
|
59746
|
+
trimWalletPortfolio(address, "solana", includeSpam)
|
|
59747
|
+
);
|
|
59042
59748
|
}
|
|
59043
59749
|
const normalized = normalizeChain(chain);
|
|
59044
59750
|
const network = ALCHEMY_NETWORKS[normalized];
|
|
@@ -59053,11 +59759,107 @@ function heroTools(endpoints, options = {}) {
|
|
|
59053
59759
|
withMetadata: true,
|
|
59054
59760
|
withPrices: true
|
|
59055
59761
|
}, "get_wallet_portfolio");
|
|
59056
|
-
return respond(
|
|
59762
|
+
return respond(
|
|
59763
|
+
result2,
|
|
59764
|
+
format,
|
|
59765
|
+
trimWalletPortfolio(address, normalized, includeSpam, true)
|
|
59766
|
+
);
|
|
59057
59767
|
}
|
|
59058
59768
|
};
|
|
59059
59769
|
return [getTokenPrice, checkTokenSafety, getWalletPortfolio];
|
|
59060
59770
|
}
|
|
59771
|
+
var FLAG_VERDICTS = {
|
|
59772
|
+
is_honeypot: "Honeypot: GoPlus says this token cannot be sold",
|
|
59773
|
+
cannot_sell_all: "Holders cannot sell their whole balance",
|
|
59774
|
+
hidden_owner: "The contract has a hidden owner",
|
|
59775
|
+
can_take_back_ownership: "Ownership can be taken back after renouncing",
|
|
59776
|
+
is_mintable: "The owner can mint more supply",
|
|
59777
|
+
transfer_pausable: "Transfers can be paused",
|
|
59778
|
+
is_blacklisted: "The contract can blacklist addresses",
|
|
59779
|
+
owner_change_balance: "The owner can change balances",
|
|
59780
|
+
selfdestruct: "The contract can self-destruct",
|
|
59781
|
+
is_airdrop_scam: "GoPlus flags this as an airdrop scam"
|
|
59782
|
+
};
|
|
59783
|
+
function percentOf(value, fraction) {
|
|
59784
|
+
const n = typeof value === "string" || typeof value === "number" ? Number(value) : NaN;
|
|
59785
|
+
if (!Number.isFinite(n)) return null;
|
|
59786
|
+
return Math.round((fraction ? n * 100 : n) * 1e3) / 1e3;
|
|
59787
|
+
}
|
|
59788
|
+
function holderView(holder) {
|
|
59789
|
+
return {
|
|
59790
|
+
address: holder.address ?? holder.account ?? null,
|
|
59791
|
+
// Solana rows use account
|
|
59792
|
+
percent: percentOf(holder.percent, true),
|
|
59793
|
+
is_contract: holder.is_contract === 1 || holder.is_contract === "1",
|
|
59794
|
+
is_locked: holder.is_locked === 1 || holder.is_locked === "1",
|
|
59795
|
+
tag: holder.tag || null
|
|
59796
|
+
};
|
|
59797
|
+
}
|
|
59798
|
+
function summarizeTokenSafety(entry2) {
|
|
59799
|
+
const verdicts = [];
|
|
59800
|
+
for (const [key, sentence] of Object.entries(FLAG_VERDICTS)) {
|
|
59801
|
+
if (entry2[key] === "1") verdicts.push(sentence);
|
|
59802
|
+
}
|
|
59803
|
+
for (const side of ["buy", "sell"]) {
|
|
59804
|
+
const tax = percentOf(entry2[`${side}_tax`], true);
|
|
59805
|
+
if (tax !== null && tax >= 10) verdicts.push(`${side === "buy" ? "Buy" : "Sell"} tax is ${tax}%`);
|
|
59806
|
+
}
|
|
59807
|
+
const lpRows = Array.isArray(entry2.lp_holders) ? entry2.lp_holders.filter(isRecord3) : [];
|
|
59808
|
+
let lp = null;
|
|
59809
|
+
if (lpRows.length > 0) {
|
|
59810
|
+
const views = lpRows.map(holderView);
|
|
59811
|
+
const top = views.reduce((a, b) => (b.percent ?? 0) > (a.percent ?? 0) ? b : a);
|
|
59812
|
+
const locked = views.reduce((sum, v) => sum + (v.is_locked ? v.percent ?? 0 : 0), 0);
|
|
59813
|
+
lp = {
|
|
59814
|
+
holder_count: entry2.lp_holder_count ?? lpRows.length,
|
|
59815
|
+
locked_percent: Math.round(locked * 1e3) / 1e3,
|
|
59816
|
+
top_holder: top
|
|
59817
|
+
};
|
|
59818
|
+
if (locked < 50 && !top.is_locked && (top.percent ?? 0) >= 50) {
|
|
59819
|
+
verdicts.push(
|
|
59820
|
+
`LP not locked: ${top.percent}% held by one ${top.is_contract ? "contract" : "EOA"}`
|
|
59821
|
+
);
|
|
59822
|
+
} else if (locked < 50) {
|
|
59823
|
+
verdicts.push(`LP mostly unlocked: only ${Math.round(locked * 1e3) / 1e3}% of LP is locked`);
|
|
59824
|
+
}
|
|
59825
|
+
}
|
|
59826
|
+
const holderRows = Array.isArray(entry2.holders) ? entry2.holders.filter(isRecord3) : [];
|
|
59827
|
+
let topHolders = null;
|
|
59828
|
+
if (holderRows.length > 0) {
|
|
59829
|
+
const views = holderRows.map(holderView);
|
|
59830
|
+
const pairs = new Set(
|
|
59831
|
+
(Array.isArray(entry2.dex) ? entry2.dex.filter(isRecord3) : []).map((d) => String(d.pair ?? "").toLowerCase())
|
|
59832
|
+
);
|
|
59833
|
+
const owners = views.filter((v) => !pairs.has(String(v.address).toLowerCase()));
|
|
59834
|
+
const top10 = owners.slice(0, 10).reduce((sum, v) => sum + (v.percent ?? 0), 0);
|
|
59835
|
+
const largest = owners[0] ?? null;
|
|
59836
|
+
topHolders = {
|
|
59837
|
+
largest,
|
|
59838
|
+
top10_percent: Math.round(top10 * 1e3) / 1e3,
|
|
59839
|
+
excludes_dex_pairs: pairs.size > 0
|
|
59840
|
+
};
|
|
59841
|
+
if (largest && (largest.percent ?? 0) >= 20) {
|
|
59842
|
+
verdicts.push(
|
|
59843
|
+
`One address holds ${largest.percent}% of supply (${largest.is_contract ? "contract" : "EOA"}, ${largest.is_locked ? "locked" : "not locked"})`
|
|
59844
|
+
);
|
|
59845
|
+
}
|
|
59846
|
+
if (top10 >= 50) verdicts.push(`Top 10 holders hold ${Math.round(top10 * 10) / 10}% of supply`);
|
|
59847
|
+
}
|
|
59848
|
+
const creator = entry2.creator_address ? {
|
|
59849
|
+
address: entry2.creator_address,
|
|
59850
|
+
balance: entry2.creator_balance ?? null,
|
|
59851
|
+
percent: percentOf(entry2.creator_percent, true)
|
|
59852
|
+
} : null;
|
|
59853
|
+
const owner = entry2.owner_address ? {
|
|
59854
|
+
address: entry2.owner_address,
|
|
59855
|
+
balance: entry2.owner_balance ?? null,
|
|
59856
|
+
percent: percentOf(entry2.owner_percent, true)
|
|
59857
|
+
} : null;
|
|
59858
|
+
for (const [label, who] of [["Creator", creator], ["Owner", owner]]) {
|
|
59859
|
+
if (who && (who.percent ?? 0) >= 5) verdicts.push(`${label} still holds ${who.percent}% of supply`);
|
|
59860
|
+
}
|
|
59861
|
+
return { verdicts, lp, top_holders: topHolders, creator, owner };
|
|
59862
|
+
}
|
|
59061
59863
|
function heroOutputSchema(name) {
|
|
59062
59864
|
return HERO_TOOL_OUTPUT_SCHEMAS[name];
|
|
59063
59865
|
}
|
|
@@ -59199,7 +60001,7 @@ var TASK_TOOLSET_ROUTES = {
|
|
|
59199
60001
|
"holders",
|
|
59200
60002
|
"Measure holder concentration with a bounded holder read.",
|
|
59201
60003
|
"get_token_top_holders",
|
|
59202
|
-
"
|
|
60004
|
+
"get_token_holder_distribution",
|
|
59203
60005
|
PROVIDER_FALLBACK
|
|
59204
60006
|
),
|
|
59205
60007
|
step(
|
|
@@ -59359,15 +60161,14 @@ var TASK_TOOLSET_ROUTES = {
|
|
|
59359
60161
|
"simulation",
|
|
59360
60162
|
"Simulate execution without broadcasting.",
|
|
59361
60163
|
"tenderly_simulate_transaction",
|
|
59362
|
-
"
|
|
60164
|
+
"tenderly_node_simulate_transaction",
|
|
59363
60165
|
PROVIDER_FALLBACK
|
|
59364
60166
|
),
|
|
59365
60167
|
step(
|
|
59366
60168
|
"risk",
|
|
59367
60169
|
"Inspect simulated asset changes.",
|
|
59368
|
-
"alchemy_simulate_asset_changes",
|
|
59369
60170
|
"simulate_evm_transaction",
|
|
59370
|
-
|
|
60171
|
+
null
|
|
59371
60172
|
)
|
|
59372
60173
|
],
|
|
59373
60174
|
stopCondition: "Stop after decoding, execution outcome, and asset effects are clear; never broadcast or mutate chain state."
|
|
@@ -60191,6 +60992,7 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
60191
60992
|
optionalIdentifiers: ["symbol", "pair address", "time range"],
|
|
60192
60993
|
recommendedTools: [
|
|
60193
60994
|
"get_token_top_holders",
|
|
60995
|
+
"get_token_holder_distribution",
|
|
60194
60996
|
"get_token_pools",
|
|
60195
60997
|
"get_token_ohlcv",
|
|
60196
60998
|
"get_token_info",
|
|
@@ -60359,8 +61161,6 @@ var TASK_TOOLSET_CATALOG = [
|
|
|
60359
61161
|
"alchemy_solana_get_compression_indexer_slot",
|
|
60360
61162
|
"alchemy_solana_get_latest_compression_signatures",
|
|
60361
61163
|
"alchemy_solana_get_latest_non_voting_signatures",
|
|
60362
|
-
"alchemy_simulate_asset_changes",
|
|
60363
|
-
"alchemy_simulate_execution",
|
|
60364
61164
|
"simulate_evm_transaction",
|
|
60365
61165
|
"defillama_get_hacks"
|
|
60366
61166
|
],
|
|
@@ -61590,7 +62390,11 @@ function getTaskOutputSchema(taskToolsetId) {
|
|
|
61590
62390
|
}
|
|
61591
62391
|
|
|
61592
62392
|
// src/services/execution-fallback.ts
|
|
61593
|
-
var FALLBACK_TRIGGER_STATUSES = /* @__PURE__ */ new Set([
|
|
62393
|
+
var FALLBACK_TRIGGER_STATUSES = /* @__PURE__ */ new Set([
|
|
62394
|
+
"failing",
|
|
62395
|
+
"missing_key",
|
|
62396
|
+
"plan_required"
|
|
62397
|
+
]);
|
|
61594
62398
|
function findRouteFallback(primaryToolName) {
|
|
61595
62399
|
for (const toolset of TASK_TOOLSETS) {
|
|
61596
62400
|
if (toolset.fallbackTools.length === 0) continue;
|
|
@@ -61633,9 +62437,7 @@ async function executeEndpointWithRouteFallback(endpoints, endpoint, args, conte
|
|
|
61633
62437
|
const fallbackProviderStatus = getProviderRuntimeStatus(
|
|
61634
62438
|
getProviderDescriptorForEndpoint(fallback)
|
|
61635
62439
|
);
|
|
61636
|
-
if (fallbackProviderStatus
|
|
61637
|
-
return primary;
|
|
61638
|
-
}
|
|
62440
|
+
if (FALLBACK_TRIGGER_STATUSES.has(fallbackProviderStatus)) return primary;
|
|
61639
62441
|
if (!argsMapCleanly(fallback, args)) return primary;
|
|
61640
62442
|
const fallbackResult = await executeEndpoint(fallback, args, {
|
|
61641
62443
|
...context,
|
|
@@ -64421,7 +65223,7 @@ async function runOAuthLifecycleHealth(overrides = {}) {
|
|
|
64421
65223
|
}
|
|
64422
65224
|
|
|
64423
65225
|
// src/health/selfTest.ts
|
|
64424
|
-
import
|
|
65226
|
+
import Coingecko4 from "@coingecko/coingecko-typescript";
|
|
64425
65227
|
|
|
64426
65228
|
// src/tools/goplus/goplusClient.ts
|
|
64427
65229
|
import { createHash as createHash8 } from "crypto";
|
|
@@ -65766,6 +66568,87 @@ var CCXTClient = class {
|
|
|
65766
66568
|
}
|
|
65767
66569
|
};
|
|
65768
66570
|
|
|
66571
|
+
// src/health/answerProbes.ts
|
|
66572
|
+
var ETH_WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
|
66573
|
+
var ETH_PEPE = "0x6982508145454Ce325dDbE47a25d4ec3d2311933";
|
|
66574
|
+
function isRecord4(value) {
|
|
66575
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
66576
|
+
}
|
|
66577
|
+
function largestArray(value, depth = 0) {
|
|
66578
|
+
if (Array.isArray(value)) return value;
|
|
66579
|
+
if (!isRecord4(value) || depth > 4) return [];
|
|
66580
|
+
let best = [];
|
|
66581
|
+
for (const child of Object.values(value)) {
|
|
66582
|
+
const candidate = largestArray(child, depth + 1);
|
|
66583
|
+
if (candidate.length > best.length) best = candidate;
|
|
66584
|
+
}
|
|
66585
|
+
return best;
|
|
66586
|
+
}
|
|
66587
|
+
function rowsOf(payload) {
|
|
66588
|
+
return isRecord4(payload) && Array.isArray(payload.rows) ? payload.rows : largestArray(payload);
|
|
66589
|
+
}
|
|
66590
|
+
function returnedCountMismatch(payload, rows) {
|
|
66591
|
+
const returned = isRecord4(payload) ? payload.returnedCount : void 0;
|
|
66592
|
+
return typeof returned === "number" && returned !== rows.length ? `returnedCount ${returned} != rows.length ${rows.length}` : null;
|
|
66593
|
+
}
|
|
66594
|
+
var ANSWER_PROBES = {
|
|
66595
|
+
polymarket_search_markets: {
|
|
66596
|
+
// The "JD Vance wins 2028" market resolves 2028-11-07; the event-level
|
|
66597
|
+
// fixture in selfTest's TOOL_PROBE_ARGS points at the same market.
|
|
66598
|
+
args: { query: "vance 2028", limit: 3 },
|
|
66599
|
+
check: (payload) => {
|
|
66600
|
+
const rows = rowsOf(payload);
|
|
66601
|
+
if (rows.length === 0)
|
|
66602
|
+
return "text search for 'vance 2028' found no markets";
|
|
66603
|
+
const unmatched = rows.filter((row) => {
|
|
66604
|
+
const text = JSON.stringify(row).toLowerCase();
|
|
66605
|
+
return !text.includes("vance") || !text.includes("2028");
|
|
66606
|
+
});
|
|
66607
|
+
if (unmatched.length > 0) {
|
|
66608
|
+
return `${unmatched.length} of ${rows.length} rows do not match every query term`;
|
|
66609
|
+
}
|
|
66610
|
+
return returnedCountMismatch(payload, rows);
|
|
66611
|
+
}
|
|
66612
|
+
},
|
|
66613
|
+
get_token_top_holders: {
|
|
66614
|
+
args: { network: "eth", address: ETH_WETH, limit: 20 },
|
|
66615
|
+
check: (payload) => {
|
|
66616
|
+
const holders = largestArray(payload);
|
|
66617
|
+
return holders.length > 10 ? null : `asked for 20 WETH holders, got ${holders.length}`;
|
|
66618
|
+
}
|
|
66619
|
+
},
|
|
66620
|
+
rwa_get_hyperliquid_dex_perps: {
|
|
66621
|
+
args: { limit: 100 },
|
|
66622
|
+
check: (payload) => {
|
|
66623
|
+
const rows = rowsOf(payload);
|
|
66624
|
+
if (rows.length === 0) return "no HIP-3 builder-dex rows";
|
|
66625
|
+
return returnedCountMismatch(payload, rows);
|
|
66626
|
+
}
|
|
66627
|
+
},
|
|
66628
|
+
// check_token_safety builds its LP verdict on this GoPlus read.
|
|
66629
|
+
get_token_security: {
|
|
66630
|
+
args: { chainId: "1", contract_addresses: ETH_PEPE },
|
|
66631
|
+
check: (payload) => {
|
|
66632
|
+
const result2 = isRecord4(payload) ? payload.result : void 0;
|
|
66633
|
+
const token = isRecord4(result2) ? Object.values(result2).find(isRecord4) : void 0;
|
|
66634
|
+
if (!token) return "no token entry in the GoPlus result";
|
|
66635
|
+
const lpHolders = token.lp_holders;
|
|
66636
|
+
return Array.isArray(lpHolders) && lpHolders.length > 0 ? null : "PEPE came back without lp_holders";
|
|
66637
|
+
}
|
|
66638
|
+
}
|
|
66639
|
+
};
|
|
66640
|
+
function checkAnswer(probe, result2) {
|
|
66641
|
+
const text = isRecord4(result2) ? result2.content?.[0]?.text : void 0;
|
|
66642
|
+
if (typeof text !== "string") return "response has no text content";
|
|
66643
|
+
let payload;
|
|
66644
|
+
try {
|
|
66645
|
+
payload = JSON.parse(text);
|
|
66646
|
+
} catch {
|
|
66647
|
+
return "response is not JSON";
|
|
66648
|
+
}
|
|
66649
|
+
return probe.check(payload);
|
|
66650
|
+
}
|
|
66651
|
+
|
|
65769
66652
|
// src/health/selfTest.ts
|
|
65770
66653
|
function positiveTimeout(name, fallbackMs) {
|
|
65771
66654
|
const configured = Number(process.env[name]);
|
|
@@ -65779,7 +66662,7 @@ var QUICK_PROBE_TIMEOUT = Math.min(
|
|
|
65779
66662
|
var ETH_WALLET = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
|
|
65780
66663
|
var MORALIS_EVM_WALLET = "0xF977814e90dA44bFA03b6295A0616a897441aceC";
|
|
65781
66664
|
var ETH_USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
|
65782
|
-
var
|
|
66665
|
+
var ETH_WETH2 = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
|
65783
66666
|
var ETH_USDT = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
|
|
65784
66667
|
var ETH_BAYC = "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D";
|
|
65785
66668
|
var ETH_USDC_WETH_PAIR = "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc";
|
|
@@ -65836,6 +66719,7 @@ var PROVIDER_RESOURCE_PATTERNS = [
|
|
|
65836
66719
|
["polymarket", /^polymarket(?:[.-]|$)/],
|
|
65837
66720
|
["alternative", /^signals-fear-greed$/],
|
|
65838
66721
|
["blockchain", /^signals-onchain/],
|
|
66722
|
+
["blockscout", /^blockscout(?:[.-]|$)/],
|
|
65839
66723
|
["fetch", /^fetch(?:[.-]|$)/],
|
|
65840
66724
|
["helius", /^helius\./],
|
|
65841
66725
|
["moralis", /^moralis\./],
|
|
@@ -65906,6 +66790,11 @@ var QUICK_PROBE_ARGS = {
|
|
|
65906
66790
|
toolName: "signals_get_onchain_metrics",
|
|
65907
66791
|
args: { metrics: ["hash_rate"], days: 1 }
|
|
65908
66792
|
},
|
|
66793
|
+
// One token read plus one holder page on Ethereum (keyless public host).
|
|
66794
|
+
blockscout: {
|
|
66795
|
+
toolName: "get_token_holder_distribution",
|
|
66796
|
+
args: { network: "eth", address: ETH_USDC, max_pages: 1 }
|
|
66797
|
+
},
|
|
65909
66798
|
fetch: {
|
|
65910
66799
|
toolName: "fetch_public_api",
|
|
65911
66800
|
args: { host: "api.coingecko.com", path: "/api/v3/ping" }
|
|
@@ -65944,7 +66833,7 @@ var FULL_MODE_DEFAULT_ARGS = {
|
|
|
65944
66833
|
vs_currency: "usd",
|
|
65945
66834
|
vs_currencies: "usd",
|
|
65946
66835
|
network: "eth",
|
|
65947
|
-
address:
|
|
66836
|
+
address: ETH_WETH2,
|
|
65948
66837
|
days: "7"
|
|
65949
66838
|
},
|
|
65950
66839
|
defillama: {
|
|
@@ -66026,7 +66915,11 @@ var TOOL_PROBE_ARGS = {
|
|
|
66026
66915
|
get_rwa_market_chart: { id: "gold", days: "7" },
|
|
66027
66916
|
get_rwa_issuer: { id: "ondo-tokenized-assets" },
|
|
66028
66917
|
// Polymarket fixtures verified live 2026-09-16; the event ends 2028-11-07.
|
|
66029
|
-
|
|
66918
|
+
get_token_holder_distribution: {
|
|
66919
|
+
network: "eth",
|
|
66920
|
+
address: ETH_USDC,
|
|
66921
|
+
max_pages: 1
|
|
66922
|
+
},
|
|
66030
66923
|
polymarket_get_events: { slug: "presidential-election-winner-2028" },
|
|
66031
66924
|
polymarket_get_market: {
|
|
66032
66925
|
slug: "will-jd-vance-win-the-2028-us-presidential-election"
|
|
@@ -67235,7 +68128,7 @@ function getClientForProvider(provider, clients) {
|
|
|
67235
68128
|
}
|
|
67236
68129
|
}
|
|
67237
68130
|
function buildClients() {
|
|
67238
|
-
const coinGeckoClient = new
|
|
68131
|
+
const coinGeckoClient = new Coingecko4(getCoinGeckoClientOptions());
|
|
67239
68132
|
return {
|
|
67240
68133
|
coinGeckoClient,
|
|
67241
68134
|
goplusClient: new GoPlusClient(
|
|
@@ -67481,7 +68374,7 @@ async function getEffectiveProbeArgs(toolName, provider, clients, args) {
|
|
|
67481
68374
|
return { [idFixture.field]: id };
|
|
67482
68375
|
});
|
|
67483
68376
|
}
|
|
67484
|
-
const toolFixture = TOOL_PROBE_ARGS[toolName];
|
|
68377
|
+
const toolFixture = ANSWER_PROBES[toolName]?.args ?? TOOL_PROBE_ARGS[toolName];
|
|
67485
68378
|
if (toolFixture) return toolFixture;
|
|
67486
68379
|
return projectArgsToToolSchema(
|
|
67487
68380
|
FULL_MODE_DEFAULT_ARGS[provider] || {},
|
|
@@ -67573,6 +68466,16 @@ async function runEndpointProbe(endpoint, provider, clients, args, timeoutMs = P
|
|
|
67573
68466
|
);
|
|
67574
68467
|
}
|
|
67575
68468
|
}
|
|
68469
|
+
const answerProbe = args ? void 0 : ANSWER_PROBES[toolName];
|
|
68470
|
+
const answerProblem = answerProbe ? checkAnswer(answerProbe, result2) : null;
|
|
68471
|
+
if (answerProblem) {
|
|
68472
|
+
return {
|
|
68473
|
+
name: toolName,
|
|
68474
|
+
status: "failed",
|
|
68475
|
+
duration,
|
|
68476
|
+
error: `Answer check failed: ${answerProblem}`
|
|
68477
|
+
};
|
|
68478
|
+
}
|
|
67576
68479
|
return { name: toolName, status: "passed", duration };
|
|
67577
68480
|
} catch (err) {
|
|
67578
68481
|
const errorMsg = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -69627,6 +70530,11 @@ var TOOLSET_INTENT_HINTS = {
|
|
|
69627
70530
|
],
|
|
69628
70531
|
security_risk: [
|
|
69629
70532
|
"rugpull",
|
|
70533
|
+
"rug pull",
|
|
70534
|
+
"rug check",
|
|
70535
|
+
"rugcheck",
|
|
70536
|
+
"honeypot",
|
|
70537
|
+
"scam",
|
|
69630
70538
|
"safe to buy",
|
|
69631
70539
|
"phishing",
|
|
69632
70540
|
"malicious",
|
|
@@ -69986,6 +70894,9 @@ function catalogEntry(endpoint) {
|
|
|
69986
70894
|
material: getMcpCreditCost(endpoint.tool.name) === 1,
|
|
69987
70895
|
credit_cost: getMcpCreditCost(endpoint.tool.name),
|
|
69988
70896
|
summary: summarizeToolDescription(endpoint.tool.description ?? ""),
|
|
70897
|
+
// Ranking text: the summary minus its negative guidance, so "Not
|
|
70898
|
+
// recommended for: ... holder info" never counts as a holder match.
|
|
70899
|
+
rankText: summarizeToolDescription(endpoint.tool.description ?? "").split(/not recommended for:|don't use when:|common mistakes:/i)[0].toLowerCase(),
|
|
69989
70900
|
searchText: [
|
|
69990
70901
|
endpoint.tool.name,
|
|
69991
70902
|
endpoint.tool.title ?? getToolTitle(endpoint.tool.name),
|
|
@@ -70004,7 +70915,11 @@ function catalogEntry(endpoint) {
|
|
|
70004
70915
|
};
|
|
70005
70916
|
}
|
|
70006
70917
|
function publicCatalogEntry(entry2) {
|
|
70007
|
-
const {
|
|
70918
|
+
const {
|
|
70919
|
+
searchText: _searchText,
|
|
70920
|
+
rankText: _rankText,
|
|
70921
|
+
...publicEntry
|
|
70922
|
+
} = entry2;
|
|
70008
70923
|
return publicEntry;
|
|
70009
70924
|
}
|
|
70010
70925
|
function normalizeSearchToken(token) {
|
|
@@ -70044,31 +70959,36 @@ function matchesSearchTokens(haystack, query) {
|
|
|
70044
70959
|
return tokens.some((token) => textMatchesSearchToken(haystack, token));
|
|
70045
70960
|
}
|
|
70046
70961
|
function queryMatchScore(entry2, query) {
|
|
70047
|
-
if (!query) return 0;
|
|
70962
|
+
if (!query) return { score: 0, direct: true };
|
|
70048
70963
|
if (!matchesSearchTokens(entry2.searchText, query)) return null;
|
|
70049
70964
|
const tokens = parseSearchTokens(query);
|
|
70050
|
-
if (tokens.length === 0) return 0;
|
|
70965
|
+
if (tokens.length === 0) return { score: 0, direct: true };
|
|
70051
70966
|
const nameTitleText = [entry2.name, entry2.title].join(" ").toLowerCase();
|
|
70967
|
+
const summaryText = entry2.rankText;
|
|
70052
70968
|
const coreText = [
|
|
70053
70969
|
entry2.name,
|
|
70054
70970
|
entry2.title,
|
|
70055
70971
|
entry2.category ?? "",
|
|
70056
70972
|
entry2.provider,
|
|
70057
|
-
|
|
70973
|
+
summaryText
|
|
70058
70974
|
].join(" ").toLowerCase();
|
|
70059
70975
|
const nameTitleTokenSet = new Set(parseSearchTokens(nameTitleText));
|
|
70060
70976
|
const coreTextTokenSet = new Set(parseSearchTokens(coreText));
|
|
70977
|
+
const providerTokenSet = new Set(parseSearchTokens(entry2.provider));
|
|
70978
|
+
let direct = false;
|
|
70061
70979
|
const score = tokens.reduce((total, token) => {
|
|
70062
70980
|
const exactToken = singularizeToken(normalizeSearchToken(token));
|
|
70063
|
-
|
|
70064
|
-
|
|
70065
|
-
if (
|
|
70066
|
-
|
|
70067
|
-
|
|
70068
|
-
|
|
70981
|
+
const providerBonus = providerTokenSet.has(exactToken) ? 8 : 0;
|
|
70982
|
+
const directScore = nameTitleTokenSet.has(exactToken) ? 24 : coreTextTokenSet.has(exactToken) ? 14 : nameTitleText.includes(exactToken) ? 12 : summaryText.includes(exactToken) ? 6 : coreText.includes(exactToken) ? 4 : 0;
|
|
70983
|
+
if (directScore > 0) {
|
|
70984
|
+
direct = true;
|
|
70985
|
+
return total + directScore + providerBonus;
|
|
70986
|
+
}
|
|
70987
|
+
if (textMatchesSearchToken(coreText, token)) return total + 2;
|
|
70988
|
+
if (textMatchesSearchToken(entry2.searchText, token)) return total + 1;
|
|
70069
70989
|
return total;
|
|
70070
70990
|
}, 0);
|
|
70071
|
-
return score > 0 ? score : null;
|
|
70991
|
+
return score > 0 ? { score, direct } : null;
|
|
70072
70992
|
}
|
|
70073
70993
|
function buildProviderByToolName(endpoints) {
|
|
70074
70994
|
const providerByToolName = /* @__PURE__ */ new Map();
|
|
@@ -70147,6 +71067,26 @@ function selectToolsetRoutes(routes, query, routeId) {
|
|
|
70147
71067
|
const positive = ranked.filter((item) => item.score > 0);
|
|
70148
71068
|
return (positive.length > 0 ? positive : ranked).slice(0, 1).map((item) => item.route);
|
|
70149
71069
|
}
|
|
71070
|
+
function promoteHealthyRouteFallbacks(route, availabilityForTool) {
|
|
71071
|
+
let promoted = false;
|
|
71072
|
+
const steps = route.steps.map((step2) => {
|
|
71073
|
+
const primaryAvailability = availabilityForTool(step2.primaryTool);
|
|
71074
|
+
const fallbackTool = step2.fallback.tool;
|
|
71075
|
+
if (!isUnavailable(primaryAvailability) || !fallbackTool || isUnavailable(availabilityForTool(fallbackTool))) {
|
|
71076
|
+
return step2;
|
|
71077
|
+
}
|
|
71078
|
+
promoted = true;
|
|
71079
|
+
return {
|
|
71080
|
+
...step2,
|
|
71081
|
+
primaryTool: fallbackTool,
|
|
71082
|
+
fallback: {
|
|
71083
|
+
tool: step2.primaryTool,
|
|
71084
|
+
when: `Promoted fallback: ${step2.primaryTool} is ${primaryAvailability} right now and returns runtime_status, not data. Original fallback condition: ${step2.fallback.when}`
|
|
71085
|
+
}
|
|
71086
|
+
};
|
|
71087
|
+
});
|
|
71088
|
+
return promoted ? { ...route, steps } : route;
|
|
71089
|
+
}
|
|
70150
71090
|
function publicRouteEntry(route) {
|
|
70151
71091
|
return {
|
|
70152
71092
|
id: route.id,
|
|
@@ -70160,9 +71100,11 @@ function publicRouteEntry(route) {
|
|
|
70160
71100
|
stopCondition: route.stopCondition
|
|
70161
71101
|
};
|
|
70162
71102
|
}
|
|
70163
|
-
function publicToolsetEntry(entry2, detail, query, routeId) {
|
|
71103
|
+
function publicToolsetEntry(entry2, detail, query, routeId, availabilityForTool = () => "ok") {
|
|
70164
71104
|
const routes = selectToolsetRoutes(entry2.routes, query, routeId).map(
|
|
70165
|
-
publicRouteEntry
|
|
71105
|
+
(route) => publicRouteEntry(
|
|
71106
|
+
promoteHealthyRouteFallbacks(route, availabilityForTool)
|
|
71107
|
+
)
|
|
70166
71108
|
);
|
|
70167
71109
|
const compact = {
|
|
70168
71110
|
id: entry2.id,
|
|
@@ -70193,6 +71135,11 @@ function publicToolsetEntry(entry2, detail, query, routeId) {
|
|
|
70193
71135
|
}
|
|
70194
71136
|
function toolsetQueryMatchScore(entry2, query) {
|
|
70195
71137
|
if (!query) return 0;
|
|
71138
|
+
if (entry2.id === "solana_analysis" && /\b0x[0-9a-f]{40}\b|\b(ethereum|base|arbitrum|optimism|polygon|bsc|avalanche)\b/i.test(
|
|
71139
|
+
query
|
|
71140
|
+
) && !/\b(solana|sol|spl|pump\.?fun|raydium|jupiter)\b/i.test(query)) {
|
|
71141
|
+
return null;
|
|
71142
|
+
}
|
|
70196
71143
|
const haystack = [
|
|
70197
71144
|
entry2.id,
|
|
70198
71145
|
entry2.label,
|
|
@@ -70368,17 +71315,15 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
70368
71315
|
const filteredTools = catalogEntries.map((entry2, index) => ({
|
|
70369
71316
|
entry: entry2,
|
|
70370
71317
|
index,
|
|
70371
|
-
|
|
70372
|
-
})).filter((item) => item.
|
|
71318
|
+
match: routeId ? { score: 100, direct: true } : queryMatchScore(entry2, query)
|
|
71319
|
+
})).filter((item) => item.match !== null).filter(
|
|
70373
71320
|
(item) => selectedRouteToolNames ? selectedRouteToolNames.has(item.entry.name) : true
|
|
70374
71321
|
).filter(
|
|
70375
71322
|
(entry2) => provider ? entry2.entry.provider.toLowerCase() === provider : true
|
|
70376
71323
|
).filter(
|
|
70377
71324
|
(entry2) => category ? entry2.entry.category?.toLowerCase() === category : true
|
|
70378
71325
|
).sort(
|
|
70379
|
-
(a, b) =>
|
|
70380
|
-
).sort(
|
|
70381
|
-
(a, b) => Number(isUnavailable(availabilityFor(a.entry.provider))) - Number(isUnavailable(availabilityFor(b.entry.provider)))
|
|
71326
|
+
(a, b) => Number(b.match.direct) - Number(a.match.direct) || Number(isUnavailable(availabilityFor(a.entry.provider))) - Number(isUnavailable(availabilityFor(b.entry.provider))) || (query ? b.match.score - a.match.score : 0) || a.index - b.index
|
|
70382
71327
|
).map((item) => item.entry);
|
|
70383
71328
|
const filteredToolsets = taskToolsetCatalogEntries.map((entry2, index) => ({
|
|
70384
71329
|
entry: entry2,
|
|
@@ -70432,7 +71377,8 @@ function dynamicTools(endpoints, options = {}) {
|
|
|
70432
71377
|
entry2,
|
|
70433
71378
|
parsed.detail ?? "compact",
|
|
70434
71379
|
query,
|
|
70435
|
-
routeId
|
|
71380
|
+
routeId,
|
|
71381
|
+
(toolName) => availabilityFor(providerByToolName.get(toolName) ?? "Hive")
|
|
70436
71382
|
);
|
|
70437
71383
|
const providerAvailability = Object.fromEntries(
|
|
70438
71384
|
entry2.providers.map((name) => [
|
|
@@ -71306,7 +72252,7 @@ function getSharedClients() {
|
|
|
71306
72252
|
);
|
|
71307
72253
|
}
|
|
71308
72254
|
sharedClients = {
|
|
71309
|
-
coinGeckoClient: new
|
|
72255
|
+
coinGeckoClient: new Coingecko5(getCoinGeckoClientOptions()),
|
|
71310
72256
|
goplusClient,
|
|
71311
72257
|
ccxtClient: new CCXTClient()
|
|
71312
72258
|
};
|
|
@@ -71339,6 +72285,7 @@ function describeToolReference(toolName, endpointIndex) {
|
|
|
71339
72285
|
};
|
|
71340
72286
|
}
|
|
71341
72287
|
function describeTaskToolset(toolset, endpointIndex) {
|
|
72288
|
+
const availability = getProviderAvailabilitySnapshot().byProviderName;
|
|
71342
72289
|
return {
|
|
71343
72290
|
id: toolset.id,
|
|
71344
72291
|
kind: toolset.kind,
|
|
@@ -71347,7 +72294,12 @@ function describeTaskToolset(toolset, endpointIndex) {
|
|
|
71347
72294
|
outcome: toolset.outcome,
|
|
71348
72295
|
requiredIdentifiers: toolset.requiredIdentifiers,
|
|
71349
72296
|
optionalIdentifiers: toolset.optionalIdentifiers,
|
|
71350
|
-
routes: toolset.routes
|
|
72297
|
+
routes: toolset.routes.map(
|
|
72298
|
+
(route) => promoteHealthyRouteFallbacks(route, (toolName) => {
|
|
72299
|
+
const endpoint = endpointIndex.get(toolName);
|
|
72300
|
+
return endpoint ? availability[getProviderNameForTool(endpoint)] ?? "ok" : "ok";
|
|
72301
|
+
})
|
|
72302
|
+
),
|
|
71351
72303
|
coverageCatalog: {
|
|
71352
72304
|
recommendedTools: toolset.recommendedTools.map(
|
|
71353
72305
|
(toolName) => describeToolReference(toolName, endpointIndex)
|