@finchagentic/mcp 4.6.1 โ 4.6.2
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/package.json +5 -6
- package/dist/_http-cache.js +0 -96
- package/dist/_text-search.js +0 -39
- package/dist/agent-loop.js +0 -301
- package/dist/annotations.js +0 -122
- package/dist/cli.js +0 -1391
- package/dist/clink-input.js +0 -15
- package/dist/config.js +0 -132
- package/dist/convex.js +0 -175
- package/dist/dex-pair.js +0 -54
- package/dist/enrichment-router.js +0 -315
- package/dist/index.js +0 -258
- package/dist/llm.js +0 -298
- package/dist/local-memory-file.js +0 -150
- package/dist/local-memory.js +0 -135
- package/dist/local-vault.js +0 -456
- package/dist/output-schemas.js +0 -605
- package/dist/project.js +0 -36
- package/dist/prompts.js +0 -111
- package/dist/public-url.js +0 -107
- package/dist/resources.js +0 -111
- package/dist/server.js +0 -322
- package/dist/signal-gate.js +0 -57
- package/dist/token-decimals.js +0 -26
- package/dist/token-gate.js +0 -88
- package/dist/tool-filter.js +0 -53
- package/dist/tools/_solidity-scan.js +0 -313
- package/dist/tools/agents.js +0 -441
- package/dist/tools/automation.js +0 -354
- package/dist/tools/base-mcp.js +0 -466
- package/dist/tools/base.js +0 -283
- package/dist/tools/chronicle.js +0 -268
- package/dist/tools/coder.js +0 -94
- package/dist/tools/deep-research.js +0 -1421
- package/dist/tools/defi.js +0 -292
- package/dist/tools/equity.js +0 -372
- package/dist/tools/events.js +0 -182
- package/dist/tools/github.js +0 -564
- package/dist/tools/insider.js +0 -264
- package/dist/tools/insight.js +0 -630
- package/dist/tools/market.js +0 -555
- package/dist/tools/memory.js +0 -1059
- package/dist/tools/miroshark.js +0 -350
- package/dist/tools/monitor.js +0 -319
- package/dist/tools/os.js +0 -236
- package/dist/tools/packets.js +0 -296
- package/dist/tools/research-chain.js +0 -226
- package/dist/tools/research-compare.js +0 -280
- package/dist/tools/research.js +0 -188
- package/dist/tools/rh-bridge.js +0 -148
- package/dist/tools/rh-mcp.js +0 -1448
- package/dist/tools/rh-orders.js +0 -556
- package/dist/tools/scanner.js +0 -564
- package/dist/tools/stake.js +0 -369
- package/dist/tools/vault.js +0 -1020
- package/dist/tools/wallet.js +0 -200
- package/dist/types.js +0 -2
- package/dist/wallet.js +0 -372
package/dist/tools/market.js
DELETED
|
@@ -1,555 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MARKET_TOOLS = void 0;
|
|
4
|
-
exports.buildTokenSnapshot = buildTokenSnapshot;
|
|
5
|
-
exports.buildBaseTokenSnapshot = buildBaseTokenSnapshot;
|
|
6
|
-
exports.buildTokenComparison = buildTokenComparison;
|
|
7
|
-
exports.buildMarketOverview = buildMarketOverview;
|
|
8
|
-
exports.buildTokenHistory = buildTokenHistory;
|
|
9
|
-
exports.fetchMarketSnapshot = fetchMarketSnapshot;
|
|
10
|
-
exports.handleMarketTool = handleMarketTool;
|
|
11
|
-
const zod_1 = require("zod");
|
|
12
|
-
const _http_cache_js_1 = require("../_http-cache.js");
|
|
13
|
-
const dex_pair_js_1 = require("../dex-pair.js");
|
|
14
|
-
const COINGECKO = "https://api.coingecko.com/api/v3";
|
|
15
|
-
const SYMBOL_TO_ID = {
|
|
16
|
-
BTC: "bitcoin", ETH: "ethereum", SOL: "solana", BNB: "binancecoin",
|
|
17
|
-
USDT: "tether", USDC: "usd-coin", XRP: "ripple", DOGE: "dogecoin",
|
|
18
|
-
ADA: "cardano", AVAX: "avalanche-2", DOT: "polkadot", LINK: "chainlink",
|
|
19
|
-
UNI: "uniswap", OP: "optimism", ARB: "arbitrum", PEPE: "pepe",
|
|
20
|
-
SUI: "sui", APT: "aptos", NEAR: "near", INJ: "injective-protocol",
|
|
21
|
-
TIA: "celestia", MATIC: "matic-network", TON: "the-open-network",
|
|
22
|
-
SHIB: "shiba-inu", WIF: "dogwifcoin", BONK: "bonk", HYPE: "hyperliquid",
|
|
23
|
-
};
|
|
24
|
-
async function cgFetch(path) {
|
|
25
|
-
// CoinGecko free tier: 30 req/min. Cache + 429-backoff lives in cachedFetch.
|
|
26
|
-
const res = await (0, _http_cache_js_1.cachedFetch)(`${COINGECKO}${path}`, {
|
|
27
|
-
headers: { Accept: "application/json" },
|
|
28
|
-
});
|
|
29
|
-
if (!res.ok)
|
|
30
|
-
throw new Error(`CoinGecko ${res.status}`);
|
|
31
|
-
return JSON.parse(res.text);
|
|
32
|
-
}
|
|
33
|
-
async function resolveTokenId(query) {
|
|
34
|
-
const upper = query.trim().toUpperCase();
|
|
35
|
-
if (SYMBOL_TO_ID[upper])
|
|
36
|
-
return { id: SYMBOL_TO_ID[upper], symbol: upper };
|
|
37
|
-
// Fallback: search CoinGecko - handles any token not in the static map
|
|
38
|
-
try {
|
|
39
|
-
const res = await cgFetch(`/search?query=${encodeURIComponent(query)}`);
|
|
40
|
-
const coin = res.coins?.[0];
|
|
41
|
-
if (coin?.id)
|
|
42
|
-
return { id: coin.id, symbol: coin.symbol?.toUpperCase() ?? upper };
|
|
43
|
-
}
|
|
44
|
-
catch { /* search failed - fall through to null */ }
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
function fmt(n, decimals = 2) {
|
|
48
|
-
if (n == null)
|
|
49
|
-
return "-";
|
|
50
|
-
return n.toLocaleString("en-US", { maximumFractionDigits: decimals });
|
|
51
|
-
}
|
|
52
|
-
function fmtPrice(n) {
|
|
53
|
-
if (n == null)
|
|
54
|
-
return "-";
|
|
55
|
-
if (n >= 1)
|
|
56
|
-
return `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
57
|
-
return `$${n.toPrecision(4)}`;
|
|
58
|
-
}
|
|
59
|
-
function fmtB(n) {
|
|
60
|
-
if (n == null)
|
|
61
|
-
return "-";
|
|
62
|
-
if (n >= 1e9)
|
|
63
|
-
return `$${(n / 1e9).toFixed(2)}B`;
|
|
64
|
-
if (n >= 1e6)
|
|
65
|
-
return `$${(n / 1e6).toFixed(1)}M`;
|
|
66
|
-
return `$${fmt(n)}`;
|
|
67
|
-
}
|
|
68
|
-
const BASE_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
|
|
69
|
-
async function fetchDexscreenerBaseToken(tokenAddress) {
|
|
70
|
-
const res = await (0, _http_cache_js_1.cachedFetch)(`https://api.dexscreener.com/latest/dex/tokens/${tokenAddress}`, {
|
|
71
|
-
headers: { Accept: "application/json" },
|
|
72
|
-
});
|
|
73
|
-
if (!res.ok)
|
|
74
|
-
return null;
|
|
75
|
-
const data = JSON.parse(res.text);
|
|
76
|
-
const pairs = (data?.pairs ?? []).filter((p) => p.chainId === "base");
|
|
77
|
-
if (!pairs.length)
|
|
78
|
-
return null;
|
|
79
|
-
// Deepest pair that our token is actually the BASE of โ the deepest pair
|
|
80
|
-
// overall is often one where it is the quote, and every figure on such a pair
|
|
81
|
-
// belongs to the other token.
|
|
82
|
-
return (0, dex_pair_js_1.pickTokenPair)(pairs, tokenAddress);
|
|
83
|
-
}
|
|
84
|
-
async function resolveCoingeckoIdByContract(tokenAddress) {
|
|
85
|
-
try {
|
|
86
|
-
const res = await cgFetch(`/coins/base/contract/${tokenAddress}`);
|
|
87
|
-
return res?.id ?? null;
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
exports.MARKET_TOOLS = [
|
|
94
|
-
{
|
|
95
|
-
name: "get_market_data",
|
|
96
|
-
description: "Get live crypto market data: top 20 coins by market cap, trending coins, and key prices for BTC/ETH/SOL.",
|
|
97
|
-
inputSchema: {
|
|
98
|
-
type: "object",
|
|
99
|
-
properties: { token: { type: "string", description: "Optional: focus on a specific token, e.g. 'BTC', 'ETH'" } },
|
|
100
|
-
required: [],
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
name: "get_token_data",
|
|
105
|
-
description: "Get live market data for a specific token. Returns price, 24h change, market cap, and volume.",
|
|
106
|
-
inputSchema: {
|
|
107
|
-
type: "object",
|
|
108
|
-
properties: { question: { type: "string", description: "Token to look up, e.g. 'ETH', 'show me SOL', 'PEPE price'" } },
|
|
109
|
-
required: ["question"],
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
name: "compare_tokens",
|
|
114
|
-
description: "Compare 2โ5 tokens side by side - price, 24h/7d change, market cap, volume, and ATH drawdown. " +
|
|
115
|
-
"Ideal for deciding between assets or tracking a portfolio watchlist.",
|
|
116
|
-
inputSchema: {
|
|
117
|
-
type: "object",
|
|
118
|
-
properties: {
|
|
119
|
-
tokens: {
|
|
120
|
-
type: "array",
|
|
121
|
-
items: { type: "string" },
|
|
122
|
-
description: "2โ5 token symbols to compare, e.g. ['BTC', 'ETH', 'SOL']",
|
|
123
|
-
minItems: 2,
|
|
124
|
-
maxItems: 5,
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
required: ["tokens"],
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
name: "market_overview",
|
|
132
|
-
description: "Global crypto market snapshot: Fear & Greed Index, BTC dominance, total market cap, DeFi TVL, " +
|
|
133
|
-
"ETH gas, trending tokens, and top sector leaders. Use for a full market briefing.",
|
|
134
|
-
inputSchema: {
|
|
135
|
-
type: "object",
|
|
136
|
-
properties: {},
|
|
137
|
-
required: [],
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
name: "token_history",
|
|
142
|
-
description: "Get historical price data for a token. Returns OHLC candles for the requested timeframe. " +
|
|
143
|
-
"Use to understand price trends, identify support/resistance levels, or calculate % changes over time.",
|
|
144
|
-
inputSchema: {
|
|
145
|
-
type: "object",
|
|
146
|
-
properties: {
|
|
147
|
-
token: { type: "string", description: "Token symbol, e.g. 'BTC', 'ETH', 'SOL'" },
|
|
148
|
-
days: {
|
|
149
|
-
type: "number",
|
|
150
|
-
description: "Number of days of history (1=24h, 7=7d, 30=30d, 90=90d, 365=1y). Default: 7",
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
required: ["token"],
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
name: "get_base_token_data",
|
|
158
|
-
description: "Get live market data for any Base-chain token by contract address, sourced from DexScreener: " +
|
|
159
|
-
"price, 1h/6h/24h change, volume, liquidity, market cap, FDV, pair age, and website/social links. " +
|
|
160
|
-
"Also checks whether the token is listed on CoinGecko (needed for historical chart data via token_history). " +
|
|
161
|
-
"Works for any Base token including small/new ones that aren't in CoinGecko's listings - use this " +
|
|
162
|
-
"instead of get_token_data when you have a contract address rather than a well-known symbol.",
|
|
163
|
-
inputSchema: {
|
|
164
|
-
type: "object",
|
|
165
|
-
properties: {
|
|
166
|
-
tokenAddress: {
|
|
167
|
-
type: "string",
|
|
168
|
-
description: "Base-chain ERC-20 contract address, e.g. '0x4b524015d54a27d4472f5c59c570730d69499ba3'",
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
required: ["tokenAddress"],
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
];
|
|
175
|
-
const GetMarketDataSchema = zod_1.z.object({ token: zod_1.z.string().optional() });
|
|
176
|
-
const GetTokenDataSchema = zod_1.z.object({ question: zod_1.z.string().min(1) });
|
|
177
|
-
const CompareTokensSchema = zod_1.z.object({ tokens: zod_1.z.array(zod_1.z.string()).min(2).max(5) });
|
|
178
|
-
const TokenHistorySchema = zod_1.z.object({ token: zod_1.z.string().min(1), days: zod_1.z.number().positive().optional() });
|
|
179
|
-
const GetBaseTokenDataSchema = zod_1.z.object({
|
|
180
|
-
tokenAddress: zod_1.z.string().regex(BASE_ADDRESS_RE, "Must be a valid 0x-prefixed 40-hex-char Base contract address"),
|
|
181
|
-
});
|
|
182
|
-
// Pure map from a CoinGecko /coins/markets row to the get_token_data
|
|
183
|
-
// structuredContent payload. Kept pure so text + structured output share one
|
|
184
|
-
// source and it's unit-testable without a network call.
|
|
185
|
-
function buildTokenSnapshot(c) {
|
|
186
|
-
return {
|
|
187
|
-
symbol: c.symbol?.toUpperCase() ?? null,
|
|
188
|
-
name: c.name ?? null,
|
|
189
|
-
priceUsd: c.current_price ?? null,
|
|
190
|
-
change24hPct: c.price_change_percentage_24h ?? null,
|
|
191
|
-
marketCapUsd: c.market_cap ?? null,
|
|
192
|
-
marketCapRank: c.market_cap_rank ?? null,
|
|
193
|
-
volume24hUsd: c.total_volume ?? null,
|
|
194
|
-
high24hUsd: c.high_24h ?? null,
|
|
195
|
-
low24hUsd: c.low_24h ?? null,
|
|
196
|
-
athUsd: c.ath ?? null,
|
|
197
|
-
athChangePct: c.ath_change_percentage ?? null,
|
|
198
|
-
source: "coingecko",
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
// Pure map from a DexScreener pair (+ optional CoinGecko id) to the
|
|
202
|
-
// get_base_token_data structuredContent payload.
|
|
203
|
-
function buildBaseTokenSnapshot(address, pair, coingeckoId) {
|
|
204
|
-
const ch = pair.priceChange ?? {};
|
|
205
|
-
const pairAgeDays = pair.pairCreatedAt ? Math.floor((Date.now() - pair.pairCreatedAt) / 86400000) : null;
|
|
206
|
-
return {
|
|
207
|
-
address,
|
|
208
|
-
symbol: pair.baseToken?.symbol ?? null,
|
|
209
|
-
name: pair.baseToken?.name ?? null,
|
|
210
|
-
priceUsd: pair.priceUsd ? parseFloat(pair.priceUsd) : null,
|
|
211
|
-
change1hPct: ch.h1 ?? null,
|
|
212
|
-
change6hPct: ch.h6 ?? null,
|
|
213
|
-
change24hPct: ch.h24 ?? null,
|
|
214
|
-
volume24hUsd: pair.volume?.h24 ?? null,
|
|
215
|
-
liquidityUsd: pair.liquidity?.usd ?? null,
|
|
216
|
-
marketCapUsd: pair.marketCap ?? null,
|
|
217
|
-
fdvUsd: pair.fdv ?? null,
|
|
218
|
-
pairAgeDays,
|
|
219
|
-
listedOnCoingecko: !!coingeckoId,
|
|
220
|
-
coingeckoId: coingeckoId ?? null,
|
|
221
|
-
source: "dexscreener",
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
// Pure map for compare_tokens: CoinGecko rows (+ unresolved symbols) โ the
|
|
225
|
-
// structuredContent payload. Includes 7d change, which the single-token
|
|
226
|
-
// snapshot doesn't carry.
|
|
227
|
-
function buildTokenComparison(data, unknown) {
|
|
228
|
-
return {
|
|
229
|
-
count: data.length,
|
|
230
|
-
unknown,
|
|
231
|
-
tokens: data.map((c) => ({
|
|
232
|
-
symbol: c.symbol?.toUpperCase() ?? null,
|
|
233
|
-
name: c.name ?? null,
|
|
234
|
-
priceUsd: c.current_price ?? null,
|
|
235
|
-
change24hPct: c.price_change_percentage_24h ?? null,
|
|
236
|
-
change7dPct: c.price_change_percentage_7d_in_currency ?? null,
|
|
237
|
-
marketCapUsd: c.market_cap ?? null,
|
|
238
|
-
marketCapRank: c.market_cap_rank ?? null,
|
|
239
|
-
volume24hUsd: c.total_volume ?? null,
|
|
240
|
-
athChangePct: c.ath_change_percentage ?? null,
|
|
241
|
-
})),
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
function buildMarketOverview(global, fg, trendCoins) {
|
|
245
|
-
return {
|
|
246
|
-
fearGreedValue: fg ? Number(fg.value) : null,
|
|
247
|
-
fearGreedClass: fg?.value_classification ?? null,
|
|
248
|
-
totalMarketCapUsd: global?.total_market_cap?.usd ?? null,
|
|
249
|
-
marketCap24hChangePct: global?.market_cap_change_percentage_24h_usd ?? null,
|
|
250
|
-
btcDominancePct: global?.market_cap_percentage?.btc ?? null,
|
|
251
|
-
ethDominancePct: global?.market_cap_percentage?.eth ?? null,
|
|
252
|
-
defiTvlUsd: global?.total_value_locked?.usd ?? null,
|
|
253
|
-
activeCoins: global?.active_cryptocurrencies ?? null,
|
|
254
|
-
trending: (trendCoins ?? []).slice(0, 7).map((t) => ({
|
|
255
|
-
symbol: t.item?.symbol ?? null,
|
|
256
|
-
name: t.item?.name ?? null,
|
|
257
|
-
rank: t.item?.market_cap_rank ?? null,
|
|
258
|
-
})),
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
function buildTokenHistory(symbol, days, current, openPrice, closePrice, periodHigh, periodLow, candles) {
|
|
262
|
-
return {
|
|
263
|
-
symbol, days,
|
|
264
|
-
currentPriceUsd: current?.current_price ?? null,
|
|
265
|
-
openPrice, closePrice,
|
|
266
|
-
periodChangePct: openPrice ? ((closePrice - openPrice) / openPrice) * 100 : null,
|
|
267
|
-
periodHighUsd: periodHigh,
|
|
268
|
-
periodLowUsd: periodLow,
|
|
269
|
-
candles: candles.slice(-30).map(([ts, o, h, l, cl]) => ({
|
|
270
|
-
date: new Date(ts).toISOString().slice(0, 10), open: o, high: h, low: l, close: cl,
|
|
271
|
-
})),
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
async function fetchMarketSnapshot() {
|
|
275
|
-
try {
|
|
276
|
-
const data = await cgFetch("/coins/markets?vs_currency=usd&ids=bitcoin,ethereum,solana&sparkline=false&price_change_percentage=24h");
|
|
277
|
-
const find = (id, field) => data.find((c) => c.id === id)?.[field] ?? 0;
|
|
278
|
-
return {
|
|
279
|
-
btc: find("bitcoin", "current_price"),
|
|
280
|
-
eth: find("ethereum", "current_price"),
|
|
281
|
-
sol: find("solana", "current_price"),
|
|
282
|
-
btcChange: find("bitcoin", "price_change_percentage_24h"),
|
|
283
|
-
ethChange: find("ethereum", "price_change_percentage_24h"),
|
|
284
|
-
solChange: find("solana", "price_change_percentage_24h"),
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
catch {
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
async function handleMarketTool(name, args) {
|
|
292
|
-
switch (name) {
|
|
293
|
-
case "get_market_data": {
|
|
294
|
-
const parsed = GetMarketDataSchema.safeParse(args ?? {});
|
|
295
|
-
if (!parsed.success)
|
|
296
|
-
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
297
|
-
const { token } = parsed.data;
|
|
298
|
-
if (token) {
|
|
299
|
-
const resolved = await resolveTokenId(token);
|
|
300
|
-
if (!resolved)
|
|
301
|
-
return { content: [{ type: "text", text: `Token not found: "${token}". Try a full name like "pepe" or a known symbol.` }], isError: true };
|
|
302
|
-
const { id, symbol: sym } = resolved;
|
|
303
|
-
const data = await cgFetch(`/coins/markets?vs_currency=usd&ids=${id}&sparkline=false&price_change_percentage=24h`);
|
|
304
|
-
const c = data[0];
|
|
305
|
-
if (!c)
|
|
306
|
-
return { content: [{ type: "text", text: `No data for ${sym}` }], isError: true };
|
|
307
|
-
const sign = (c.price_change_percentage_24h ?? 0) >= 0 ? "+" : "";
|
|
308
|
-
const lines = [
|
|
309
|
-
`**${c.symbol?.toUpperCase()} - ${c.name}**`,
|
|
310
|
-
`Price: ${fmtPrice(c.current_price)} (${sign}${fmt(c.price_change_percentage_24h)}% 24h)`,
|
|
311
|
-
`Market Cap: ${fmtB(c.market_cap)} (rank #${c.market_cap_rank ?? "-"})`,
|
|
312
|
-
`Volume 24h: ${fmtB(c.total_volume)}`,
|
|
313
|
-
`High/Low 24h: ${fmtPrice(c.high_24h)} / ${fmtPrice(c.low_24h)}`,
|
|
314
|
-
"",
|
|
315
|
-
`_Source: CoinGecko ยท ${new Date().toUTCString()}_`,
|
|
316
|
-
];
|
|
317
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
318
|
-
}
|
|
319
|
-
const [top20, trending] = await Promise.all([
|
|
320
|
-
cgFetch("/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=20&page=1&sparkline=false&price_change_percentage=24h"),
|
|
321
|
-
cgFetch("/search/trending"),
|
|
322
|
-
]);
|
|
323
|
-
const lines = [`**Crypto Market Overview** - ${new Date().toUTCString()}`, ""];
|
|
324
|
-
lines.push("**Key Prices**");
|
|
325
|
-
for (const sym of ["BTC", "ETH", "SOL"]) {
|
|
326
|
-
const c = top20.find((x) => x.symbol?.toUpperCase() === sym);
|
|
327
|
-
if (!c)
|
|
328
|
-
continue;
|
|
329
|
-
const sign = (c.price_change_percentage_24h ?? 0) >= 0 ? "+" : "";
|
|
330
|
-
lines.push(`โข **${sym}**: ${fmtPrice(c.current_price)} (${sign}${fmt(c.price_change_percentage_24h)}% 24h) - mcap ${fmtB(c.market_cap)}`);
|
|
331
|
-
}
|
|
332
|
-
lines.push("", "**Top 20 by Market Cap**");
|
|
333
|
-
for (const c of top20) {
|
|
334
|
-
const sym = c.symbol?.toUpperCase();
|
|
335
|
-
const sign = (c.price_change_percentage_24h ?? 0) >= 0 ? "+" : "";
|
|
336
|
-
lines.push(`${c.market_cap_rank}. **${sym}** ${fmtPrice(c.current_price)} (${sign}${fmt(c.price_change_percentage_24h)}%) - ${fmtB(c.market_cap)}`);
|
|
337
|
-
}
|
|
338
|
-
const trendingCoins = trending?.coins?.slice(0, 7) ?? [];
|
|
339
|
-
if (trendingCoins.length > 0) {
|
|
340
|
-
lines.push("", "**Trending**");
|
|
341
|
-
for (const t of trendingCoins) {
|
|
342
|
-
const item = t.item;
|
|
343
|
-
lines.push(`โข **${item.symbol}** (#${item.market_cap_rank ?? "-"}) - ${item.name}`);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
lines.push("", `_Source: CoinGecko_`);
|
|
347
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
348
|
-
}
|
|
349
|
-
case "get_token_data": {
|
|
350
|
-
const parsed = GetTokenDataSchema.safeParse(args);
|
|
351
|
-
if (!parsed.success)
|
|
352
|
-
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
353
|
-
const q = parsed.data.question;
|
|
354
|
-
// Try to extract a known symbol first, then fall back to search
|
|
355
|
-
const upperQ = q.toUpperCase();
|
|
356
|
-
const knownSym = Object.keys(SYMBOL_TO_ID).find((s) => new RegExp(`\\b${s}\\b`).test(upperQ));
|
|
357
|
-
const resolved = await resolveTokenId(knownSym ?? q);
|
|
358
|
-
if (!resolved)
|
|
359
|
-
return { content: [{ type: "text", text: `Token not found: "${q}". Try a symbol like "ETH" or a full name.` }], isError: true };
|
|
360
|
-
const { id, symbol: sym } = resolved;
|
|
361
|
-
const data = await cgFetch(`/coins/markets?vs_currency=usd&ids=${id}&sparkline=false&price_change_percentage=24h`);
|
|
362
|
-
const c = data[0];
|
|
363
|
-
if (!c)
|
|
364
|
-
return { content: [{ type: "text", text: `No data found for ${sym}` }], isError: true };
|
|
365
|
-
const sign = (c.price_change_percentage_24h ?? 0) >= 0 ? "+" : "";
|
|
366
|
-
const lines = [
|
|
367
|
-
`**${c.symbol?.toUpperCase()} - ${c.name}**`,
|
|
368
|
-
`Price: ${fmtPrice(c.current_price)} (${sign}${fmt(c.price_change_percentage_24h)}% 24h)`,
|
|
369
|
-
`Market Cap: ${fmtB(c.market_cap)} (rank #${c.market_cap_rank ?? "-"})`,
|
|
370
|
-
`Volume 24h: ${fmtB(c.total_volume)}`,
|
|
371
|
-
`High/Low 24h: ${fmtPrice(c.high_24h)} / ${fmtPrice(c.low_24h)}`,
|
|
372
|
-
`All-Time High: ${fmtPrice(c.ath)} (${c.ath_change_percentage != null ? fmt(c.ath_change_percentage) + "% from ATH" : "-"})`,
|
|
373
|
-
"",
|
|
374
|
-
`_Source: CoinGecko ยท ${new Date().toUTCString()}_`,
|
|
375
|
-
];
|
|
376
|
-
return {
|
|
377
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
378
|
-
structuredContent: buildTokenSnapshot(c),
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
case "compare_tokens": {
|
|
382
|
-
const parsed = CompareTokensSchema.safeParse(args);
|
|
383
|
-
if (!parsed.success)
|
|
384
|
-
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
385
|
-
const syms = parsed.data.tokens.map(t => t.toUpperCase());
|
|
386
|
-
const ids = syms.map(s => SYMBOL_TO_ID[s]).filter(Boolean);
|
|
387
|
-
const unknown = syms.filter(s => !SYMBOL_TO_ID[s]);
|
|
388
|
-
if (!ids.length)
|
|
389
|
-
return { content: [{ type: "text", text: `Unknown tokens: ${unknown.join(", ")}` }], isError: true };
|
|
390
|
-
const data = await cgFetch(`/coins/markets?vs_currency=usd&ids=${ids.join(",")}&sparkline=false&price_change_percentage=24h,7d`);
|
|
391
|
-
const header = [
|
|
392
|
-
`**Token Comparison** - ${new Date().toUTCString()}`,
|
|
393
|
-
unknown.length ? `\nโ ๏ธ Unknown: ${unknown.join(", ")}` : "",
|
|
394
|
-
``,
|
|
395
|
-
`| Token | Price | 24h | 7d | Mcap | Vol 24h | ATH% |`,
|
|
396
|
-
`|-------|-------|-----|----|------|---------|------|`,
|
|
397
|
-
].filter(Boolean);
|
|
398
|
-
const rows = data.map((c) => {
|
|
399
|
-
const sym = c.symbol?.toUpperCase();
|
|
400
|
-
const ch24 = c.price_change_percentage_24h ?? 0;
|
|
401
|
-
const ch7d = c.price_change_percentage_7d_in_currency ?? 0;
|
|
402
|
-
const athPct = c.ath_change_percentage ?? 0;
|
|
403
|
-
const s = (n) => `${n >= 0 ? "+" : ""}${fmt(n)}%`;
|
|
404
|
-
return `| **${sym}** | ${fmtPrice(c.current_price)} | ${s(ch24)} | ${s(ch7d)} | ${fmtB(c.market_cap)} | ${fmtB(c.total_volume)} | ${fmt(athPct)}% |`;
|
|
405
|
-
});
|
|
406
|
-
return {
|
|
407
|
-
content: [{ type: "text", text: [...header, ...rows].join("\n") }],
|
|
408
|
-
structuredContent: buildTokenComparison(data, unknown),
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
case "market_overview": {
|
|
412
|
-
const [globalData, fearGreed, trending] = await Promise.allSettled([
|
|
413
|
-
cgFetch("/global"),
|
|
414
|
-
(0, _http_cache_js_1.cachedFetch)("https://api.alternative.me/fng/", { headers: { Accept: "application/json" } }, { timeoutMs: 8000 })
|
|
415
|
-
.then((r) => { if (!r.ok)
|
|
416
|
-
throw new Error(`fng ${r.status}`); return JSON.parse(r.text); }),
|
|
417
|
-
cgFetch("/search/trending"),
|
|
418
|
-
]);
|
|
419
|
-
const global = globalData.status === "fulfilled" ? globalData.value.data : null;
|
|
420
|
-
const fg = fearGreed.status === "fulfilled" ? fearGreed.value?.data?.[0] : null;
|
|
421
|
-
const trendCoins = trending.status === "fulfilled" ? (trending.value?.coins ?? []) : [];
|
|
422
|
-
const totalMcap = global?.total_market_cap?.usd;
|
|
423
|
-
const defiTvl = global?.total_value_locked?.usd;
|
|
424
|
-
const btcDom = global?.market_cap_percentage?.btc;
|
|
425
|
-
const ethDom = global?.market_cap_percentage?.eth;
|
|
426
|
-
const mcap24hChange = global?.market_cap_change_percentage_24h_usd;
|
|
427
|
-
const fgEmoji = fg ? (Number(fg.value) >= 75 ? "๐ข Extreme Greed" : Number(fg.value) >= 55 ? "๐ข Greed" : Number(fg.value) >= 45 ? "๐ก Neutral" : Number(fg.value) >= 25 ? "๐ด Fear" : "๐ด Extreme Fear") : "";
|
|
428
|
-
const lines = [
|
|
429
|
-
`## ๐ Global Crypto Market`,
|
|
430
|
-
`_${new Date().toUTCString()}_`,
|
|
431
|
-
``,
|
|
432
|
-
`**Fear & Greed:** ${fgEmoji} ${fg?.value ?? "-"}/100 (${fg?.value_classification ?? "-"})`,
|
|
433
|
-
totalMcap ? `**Total Market Cap:** ${fmtB(totalMcap)} (${mcap24hChange != null ? `${mcap24hChange >= 0 ? "+" : ""}${fmt(mcap24hChange)}% 24h` : ""})` : "",
|
|
434
|
-
btcDom != null ? `**BTC Dominance:** ${fmt(btcDom)}% | **ETH:** ${fmt(ethDom ?? 0)}%` : "",
|
|
435
|
-
defiTvl ? `**DeFi TVL:** ${fmtB(defiTvl)}` : "",
|
|
436
|
-
global?.active_cryptocurrencies ? `**Active Coins:** ${global.active_cryptocurrencies.toLocaleString()}` : "",
|
|
437
|
-
``,
|
|
438
|
-
].filter(l => l !== "");
|
|
439
|
-
if (trendCoins.length > 0) {
|
|
440
|
-
lines.push(`**๐ฅ Trending Now**`);
|
|
441
|
-
for (const t of trendCoins.slice(0, 7)) {
|
|
442
|
-
const item = t.item;
|
|
443
|
-
const rank = item.market_cap_rank ? `#${item.market_cap_rank}` : "unranked";
|
|
444
|
-
lines.push(`โข **${item.symbol}** (${rank}) - ${item.name}`);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent: buildMarketOverview(global, fg, trendCoins) };
|
|
448
|
-
}
|
|
449
|
-
case "token_history": {
|
|
450
|
-
const parsed = TokenHistorySchema.safeParse(args);
|
|
451
|
-
if (!parsed.success)
|
|
452
|
-
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
453
|
-
const days = parsed.data.days ?? 7;
|
|
454
|
-
const resolved = await resolveTokenId(parsed.data.token);
|
|
455
|
-
if (!resolved)
|
|
456
|
-
return { content: [{ type: "text", text: `Token not found: "${parsed.data.token}". Try a symbol like "ETH" or a full name.` }], isError: true };
|
|
457
|
-
const { id, symbol: sym } = resolved;
|
|
458
|
-
const [ohlc, current] = await Promise.all([
|
|
459
|
-
cgFetch(`/coins/${id}/ohlc?vs_currency=usd&days=${days}`),
|
|
460
|
-
cgFetch(`/coins/markets?vs_currency=usd&ids=${id}&sparkline=false&price_change_percentage=24h`),
|
|
461
|
-
]);
|
|
462
|
-
const c = current[0];
|
|
463
|
-
const candles = ohlc ?? [];
|
|
464
|
-
if (!candles.length)
|
|
465
|
-
return { content: [{ type: "text", text: `No history data for ${sym}` }], isError: true };
|
|
466
|
-
const first = candles[0];
|
|
467
|
-
const last = candles[candles.length - 1];
|
|
468
|
-
const openPrice = first[1];
|
|
469
|
-
const closePrice = last[4];
|
|
470
|
-
const periodChange = ((closePrice - openPrice) / openPrice) * 100;
|
|
471
|
-
const highs = candles.map(c => c[2]);
|
|
472
|
-
const lows = candles.map(c => c[3]);
|
|
473
|
-
const periodHigh = Math.max(...highs);
|
|
474
|
-
const periodLow = Math.min(...lows);
|
|
475
|
-
const lines = [
|
|
476
|
-
`## ${sym} - ${days}d History`,
|
|
477
|
-
``,
|
|
478
|
-
`**Current:** ${fmtPrice(c?.current_price)} (${(c?.price_change_percentage_24h ?? 0) >= 0 ? "+" : ""}${fmt(c?.price_change_percentage_24h)}% 24h)`,
|
|
479
|
-
`**Period open:** ${fmtPrice(openPrice)}`,
|
|
480
|
-
`**Period close:** ${fmtPrice(closePrice)} (${periodChange >= 0 ? "+" : ""}${fmt(periodChange)}% over ${days}d)`,
|
|
481
|
-
`**${days}d High:** ${fmtPrice(periodHigh)}`,
|
|
482
|
-
`**${days}d Low:** ${fmtPrice(periodLow)}`,
|
|
483
|
-
`**Range:** ${fmt((periodHigh - periodLow) / periodLow * 100)}% spread`,
|
|
484
|
-
``,
|
|
485
|
-
`**Last 10 candles (OHLC):**`,
|
|
486
|
-
`| Date | Open | High | Low | Close |`,
|
|
487
|
-
`|------|------|------|-----|-------|`,
|
|
488
|
-
...candles.slice(-10).map(([ts, o, h, l, cl]) => {
|
|
489
|
-
const d = new Date(ts).toISOString().slice(0, 10);
|
|
490
|
-
return `| ${d} | ${fmtPrice(o)} | ${fmtPrice(h)} | ${fmtPrice(l)} | ${fmtPrice(cl)} |`;
|
|
491
|
-
}),
|
|
492
|
-
];
|
|
493
|
-
return {
|
|
494
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
495
|
-
structuredContent: buildTokenHistory(sym, days, c, openPrice, closePrice, periodHigh, periodLow, candles),
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
case "get_base_token_data": {
|
|
499
|
-
const parsed = GetBaseTokenDataSchema.safeParse(args);
|
|
500
|
-
if (!parsed.success)
|
|
501
|
-
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
502
|
-
const address = parsed.data.tokenAddress.toLowerCase();
|
|
503
|
-
const [pair, coingeckoId] = await Promise.all([
|
|
504
|
-
fetchDexscreenerBaseToken(address),
|
|
505
|
-
resolveCoingeckoIdByContract(address),
|
|
506
|
-
]);
|
|
507
|
-
if (!pair) {
|
|
508
|
-
return {
|
|
509
|
-
content: [{ type: "text", text: `No Base-chain liquidity pair found for ${address}. It may not be a Base token, may have no active DEX pair, or the address may be wrong.` }],
|
|
510
|
-
isError: true,
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
const priceUsd = pair.priceUsd ? parseFloat(pair.priceUsd) : null;
|
|
514
|
-
const ch = pair.priceChange ?? {};
|
|
515
|
-
const sign = (n) => (typeof n === "number" && n >= 0 ? "+" : "");
|
|
516
|
-
const pairAgeDays = pair.pairCreatedAt ? Math.floor((Date.now() - pair.pairCreatedAt) / 86400000) : null;
|
|
517
|
-
const lines = [
|
|
518
|
-
`**${pair.baseToken?.symbol ?? "?"} - ${pair.baseToken?.name ?? "Unknown token"}** (Base)`,
|
|
519
|
-
`Contract: \`${address}\``,
|
|
520
|
-
`Price: ${fmtPrice(priceUsd)}`,
|
|
521
|
-
`Change: 1h ${sign(ch.h1)}${fmt(ch.h1)}% ยท 6h ${sign(ch.h6)}${fmt(ch.h6)}% ยท 24h ${sign(ch.h24)}${fmt(ch.h24)}%`,
|
|
522
|
-
`Volume 24h: ${fmtB(pair.volume?.h24)}`,
|
|
523
|
-
`Liquidity: ${fmtB(pair.liquidity?.usd)}`,
|
|
524
|
-
`Market Cap: ${fmtB(pair.marketCap)}`,
|
|
525
|
-
`FDV: ${fmtB(pair.fdv)}`,
|
|
526
|
-
];
|
|
527
|
-
if (pairAgeDays != null)
|
|
528
|
-
lines.push(`Pair age: ${pairAgeDays}d`);
|
|
529
|
-
if (pair.derivedFromQuoteSide) {
|
|
530
|
-
lines.push("", `_This token only appears as the quote side of its pools, so the price above is derived ` +
|
|
531
|
-
`from the pair ratio rather than quoted directly. Market cap, FDV, 24h change and trade ` +
|
|
532
|
-
`counts are omitted because on such a pair they describe the other token._`);
|
|
533
|
-
}
|
|
534
|
-
lines.push("", coingeckoId
|
|
535
|
-
? `๐ Listed on CoinGecko as \`${coingeckoId}\` - historical chart data is available (use token_history).`
|
|
536
|
-
: `โ ๏ธ Not listed on CoinGecko - no historical chart available, live DexScreener data only.`);
|
|
537
|
-
const websites = pair.info?.websites ?? [];
|
|
538
|
-
const socials = pair.info?.socials ?? [];
|
|
539
|
-
if (websites.length || socials.length) {
|
|
540
|
-
lines.push("", "**Links**");
|
|
541
|
-
for (const w of websites)
|
|
542
|
-
lines.push(`โข ${w.label || "Website"}: ${w.url}`);
|
|
543
|
-
for (const s of socials)
|
|
544
|
-
lines.push(`โข ${s.type}: ${s.url}`);
|
|
545
|
-
}
|
|
546
|
-
lines.push("", `_Source: DexScreener${coingeckoId ? " + CoinGecko" : ""} ยท ${new Date().toUTCString()}_`);
|
|
547
|
-
return {
|
|
548
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
549
|
-
structuredContent: buildBaseTokenSnapshot(address, pair, coingeckoId),
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
default:
|
|
553
|
-
return null;
|
|
554
|
-
}
|
|
555
|
-
}
|