@aibtc/mcp-server 1.34.0 → 1.36.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 +7 -0
- package/dist/config/contracts.d.ts +14 -14
- package/dist/config/contracts.js +7 -7
- package/dist/config/contracts.js.map +1 -1
- package/dist/services/mempool-api.d.ts +64 -0
- package/dist/services/mempool-api.d.ts.map +1 -1
- package/dist/services/mempool-api.js +44 -0
- package/dist/services/mempool-api.js.map +1 -1
- package/dist/services/tenero-api.d.ts +76 -0
- package/dist/services/tenero-api.d.ts.map +1 -0
- package/dist/services/tenero-api.js +108 -0
- package/dist/services/tenero-api.js.map +1 -0
- package/dist/tools/dual-stacking.tools.d.ts +3 -0
- package/dist/tools/dual-stacking.tools.d.ts.map +1 -0
- package/dist/tools/dual-stacking.tools.js +281 -0
- package/dist/tools/dual-stacking.tools.js.map +1 -0
- package/dist/tools/inbox.tools.d.ts.map +1 -1
- package/dist/tools/inbox.tools.js +5 -0
- package/dist/tools/inbox.tools.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +42 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mempool.tools.d.ts +13 -0
- package/dist/tools/mempool.tools.d.ts.map +1 -0
- package/dist/tools/mempool.tools.js +125 -0
- package/dist/tools/mempool.tools.js.map +1 -0
- package/dist/tools/relay-diagnostic.tools.d.ts.map +1 -1
- package/dist/tools/relay-diagnostic.tools.js +67 -4
- package/dist/tools/relay-diagnostic.tools.js.map +1 -1
- package/dist/tools/signing.tools.d.ts.map +1 -1
- package/dist/tools/signing.tools.js +19 -6
- package/dist/tools/signing.tools.js.map +1 -1
- package/dist/tools/skill-mappings.d.ts +15 -0
- package/dist/tools/skill-mappings.d.ts.map +1 -0
- package/dist/tools/skill-mappings.js +237 -0
- package/dist/tools/skill-mappings.js.map +1 -0
- package/dist/tools/tenero.tools.d.ts +22 -0
- package/dist/tools/tenero.tools.d.ts.map +1 -0
- package/dist/tools/tenero.tools.js +318 -0
- package/dist/tools/tenero.tools.js.map +1 -0
- package/dist/utils/relay-health.d.ts +28 -0
- package/dist/utils/relay-health.d.ts.map +1 -1
- package/dist/utils/relay-health.js +157 -7
- package/dist/utils/relay-health.js.map +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-mappings.js","sourceRoot":"","sources":["../../src/tools/skill-mappings.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,mDAAmD;IACnD,eAAe,EAAE,QAAQ;IACzB,aAAa,EAAE,QAAQ;IACvB,aAAa,EAAE,QAAQ;IACvB,aAAa,EAAE,QAAQ;IACvB,WAAW,EAAE,QAAQ;IACrB,WAAW,EAAE,QAAQ;IACrB,aAAa,EAAE,QAAQ;IACvB,aAAa,EAAE,QAAQ;IACvB,aAAa,EAAE,QAAQ;IACvB,sBAAsB,EAAE,QAAQ;IAChC,kBAAkB,EAAE,QAAQ;IAC5B,aAAa,EAAE,QAAQ;IAEvB,2DAA2D;IAC3D,eAAe,EAAE,KAAK;IACtB,YAAY,EAAE,KAAK;IACnB,qBAAqB,EAAE,KAAK;IAC5B,aAAa,EAAE,KAAK;IACpB,eAAe,EAAE,KAAK;IACtB,sBAAsB,EAAE,KAAK;IAE7B,oCAAoC;IACpC,eAAe,EAAE,KAAK;IACtB,YAAY,EAAE,KAAK;IACnB,aAAa,EAAE,KAAK;IACpB,YAAY,EAAE,KAAK;IACnB,kBAAkB,EAAE,KAAK;IACzB,iBAAiB,EAAE,KAAK;IACxB,2BAA2B,EAAE,KAAK;IAClC,oBAAoB,EAAE,KAAK;IAC3B,0BAA0B,EAAE,KAAK;IACjC,mBAAmB,EAAE,KAAK;IAE1B,qCAAqC;IACrC,gBAAgB,EAAE,MAAM;IACxB,aAAa,EAAE,MAAM;IACrB,qBAAqB,EAAE,MAAM;IAC7B,iBAAiB,EAAE,MAAM;IACzB,YAAY,EAAE,MAAM;IACpB,mBAAmB,EAAE,MAAM;IAC3B,wBAAwB,EAAE,MAAM;IAChC,aAAa,EAAE,MAAM;IACrB,sBAAsB,EAAE,MAAM;IAC9B,oBAAoB,EAAE,MAAM;IAE5B,mDAAmD;IACnD,iBAAiB,EAAE,QAAQ;IAC3B,cAAc,EAAE,QAAQ;IACxB,gBAAgB,EAAE,QAAQ;IAC1B,iBAAiB,EAAE,QAAQ;IAC3B,cAAc,EAAE,QAAQ;IAExB,qCAAqC;IACrC,gBAAgB,EAAE,KAAK;IACvB,gBAAgB,EAAE,KAAK;IACvB,YAAY,EAAE,KAAK;IACnB,aAAa,EAAE,KAAK;IACpB,mBAAmB,EAAE,KAAK;IAC1B,eAAe,EAAE,KAAK;IAEtB,mDAAmD;IACnD,YAAY,EAAE,UAAU;IACxB,mBAAmB,EAAE,UAAU;IAC/B,SAAS,EAAE,UAAU;IACrB,eAAe,EAAE,UAAU;IAE3B,0DAA0D;IAC1D,oBAAoB,EAAE,eAAe;IACrC,yBAAyB,EAAE,eAAe;IAC1C,oBAAoB,EAAE,eAAe;IACrC,qBAAqB,EAAE,eAAe;IAEtC,6CAA6C;IAC7C,eAAe,EAAE,KAAK;IACtB,kBAAkB,EAAE,KAAK;IACzB,YAAY,EAAE,KAAK;IACnB,sBAAsB,EAAE,KAAK;IAC7B,aAAa,EAAE,KAAK;IACpB,iBAAiB,EAAE,KAAK;IACxB,mBAAmB,EAAE,KAAK;IAC1B,iBAAiB,EAAE,KAAK;IACxB,iBAAiB,EAAE,KAAK;IAExB,sDAAsD;IACtD,YAAY,EAAE,OAAO;IACrB,gBAAgB,EAAE,OAAO;IACzB,wBAAwB,EAAE,OAAO;IACjC,cAAc,EAAE,OAAO;IACvB,gBAAgB,EAAE,OAAO;IACzB,iBAAiB,EAAE,OAAO;IAC1B,mBAAmB,EAAE,OAAO;IAC5B,kBAAkB,EAAE,OAAO;IAC3B,uBAAuB,EAAE,OAAO;IAEhC,mEAAmE;IACnE,mBAAmB,EAAE,MAAM;IAC3B,qBAAqB,EAAE,MAAM;IAC7B,mBAAmB,EAAE,MAAM;IAC3B,sBAAsB,EAAE,MAAM;IAC9B,yBAAyB,EAAE,MAAM;IACjC,4BAA4B,EAAE,MAAM;IACpC,iBAAiB,EAAE,MAAM;IACzB,kBAAkB,EAAE,MAAM;IAE1B,0CAA0C;IAC1C,eAAe,EAAE,MAAM;IACvB,mBAAmB,EAAE,MAAM;IAC3B,SAAS,EAAE,MAAM;IACjB,kBAAkB,EAAE,MAAM;IAC1B,gBAAgB,EAAE,MAAM;IACxB,iBAAiB,EAAE,MAAM;IACzB,WAAW,EAAE,MAAM;IACnB,aAAa,EAAE,MAAM;IACrB,WAAW,EAAE,MAAM;IACnB,UAAU,EAAE,MAAM;IAElB,qDAAqD;IACrD,gBAAgB,EAAE,MAAM;IACxB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,MAAM;IACjB,UAAU,EAAE,MAAM;IAClB,YAAY,EAAE,MAAM;IACpB,WAAW,EAAE,MAAM;IACnB,YAAY,EAAE,MAAM;IAEpB,4DAA4D;IAC5D,kBAAkB,EAAE,cAAc;IAClC,iBAAiB,EAAE,cAAc;IACjC,mBAAmB,EAAE,cAAc;IACnC,sBAAsB,EAAE,cAAc;IAEtC,mFAAmF;IACnF,cAAc,EAAE,QAAQ;IACxB,iBAAiB,EAAE,QAAQ;IAC3B,aAAa,EAAE,QAAQ;IACvB,WAAW,EAAE,QAAQ;IACrB,WAAW,EAAE,QAAQ;IACrB,gBAAgB,EAAE,QAAQ;IAC1B,aAAa,EAAE,QAAQ;IACvB,oBAAoB,EAAE,QAAQ;IAC9B,aAAa,EAAE,QAAQ;IACvB,YAAY,EAAE,QAAQ;IACtB,eAAe,EAAE,QAAQ;IACzB,oBAAoB,EAAE,QAAQ;IAC9B,aAAa,EAAE,QAAQ;IACvB,iBAAiB,EAAE,QAAQ;IAC3B,mBAAmB,EAAE,QAAQ;IAC7B,sBAAsB,EAAE,QAAQ;IAChC,iBAAiB,EAAE,QAAQ;IAC3B,mBAAmB,EAAE,QAAQ;IAC7B,iBAAiB,EAAE,QAAQ;IAC3B,eAAe,EAAE,QAAQ;IACzB,eAAe,EAAE,QAAQ;IACzB,mBAAmB,EAAE,QAAQ;IAC7B,oBAAoB,EAAE,QAAQ;IAC9B,oBAAoB,EAAE,QAAQ;IAC9B,kBAAkB,EAAE,QAAQ;IAC5B,2BAA2B,EAAE,QAAQ;IACrC,sBAAsB,EAAE,QAAQ;IAChC,iCAAiC,EAAE,QAAQ;IAC3C,uBAAuB,EAAE,QAAQ;IACjC,2BAA2B,EAAE,QAAQ;IACrC,wBAAwB,EAAE,QAAQ;IAClC,0BAA0B,EAAE,QAAQ;IACpC,6BAA6B,EAAE,QAAQ;IACvC,wBAAwB,EAAE,QAAQ;IAClC,mBAAmB,EAAE,QAAQ;IAC7B,+BAA+B,EAAE,QAAQ;IACzC,uBAAuB,EAAE,QAAQ;IACjC,8BAA8B,EAAE,QAAQ;IACxC,6BAA6B,EAAE,QAAQ;IAEvC,6DAA6D;IAC7D,gBAAgB,EAAE,UAAU;IAC5B,gBAAgB,EAAE,UAAU;IAC5B,mBAAmB,EAAE,UAAU;IAC/B,kBAAkB,EAAE,UAAU;IAC9B,kBAAkB,EAAE,UAAU;IAC9B,qBAAqB,EAAE,UAAU;IACjC,kBAAkB,EAAE,UAAU;IAC9B,kBAAkB,EAAE,UAAU;IAC9B,qBAAqB,EAAE,UAAU;IAEjC,mDAAmD;IACnD,mBAAmB,EAAE,SAAS;IAC9B,qBAAqB,EAAE,SAAS;IAChC,gBAAgB,EAAE,SAAS;IAC3B,kBAAkB,EAAE,SAAS;IAC7B,mBAAmB,EAAE,SAAS;IAC9B,qBAAqB,EAAE,SAAS;IAChC,gBAAgB,EAAE,SAAS;IAE3B,2DAA2D;IAC3D,mBAAmB,EAAE,UAAU;IAC/B,wBAAwB,EAAE,UAAU;IACpC,QAAQ,EAAE,UAAU;IACpB,eAAe,EAAE,UAAU;IAC3B,eAAe,EAAE,UAAU;IAC3B,cAAc,EAAE,UAAU;IAC1B,qBAAqB,EAAE,UAAU;IACjC,8BAA8B,EAAE,UAAU;IAE1C,8DAA8D;IAC9D,WAAW,EAAE,cAAc;IAC3B,SAAS,EAAE,cAAc;IACzB,cAAc,EAAE,cAAc;IAC9B,uBAAuB,EAAE,cAAc;IAEvC,oDAAoD;IACpD,iBAAiB,EAAE,UAAU;IAC7B,YAAY,EAAE,UAAU;IAExB,wDAAwD;IACxD,aAAa,EAAE,YAAY;IAC3B,cAAc,EAAE,YAAY;IAE5B,wDAAwD;IACxD,kBAAkB,EAAE,YAAY;IAChC,qBAAqB,EAAE,YAAY;IACnC,sBAAsB,EAAE,YAAY;IAEpC,0CAA0C;IAC1C,mBAAmB,EAAE,WAAW;IAChC,uBAAuB,EAAE,WAAW;IACpC,kBAAkB,EAAE,WAAW;IAC/B,mBAAmB,EAAE,WAAW;IAChC,uBAAuB,EAAE,WAAW;IACpC,oBAAoB,EAAE,WAAW;IAEjC,yCAAyC;IACzC,kBAAkB,EAAE,SAAS;IAC7B,kBAAkB,EAAE,SAAS;IAC7B,wBAAwB,EAAE,SAAS;IACnC,iBAAiB,EAAE,SAAS;IAC5B,kBAAkB,EAAE,SAAS;IAC7B,YAAY,EAAE,SAAS;IACvB,2BAA2B,EAAE,SAAS;IACtC,oBAAoB,EAAE,SAAS;IAC/B,iBAAiB,EAAE,SAAS;IAC5B,oBAAoB,EAAE,SAAS;IAC/B,uBAAuB,EAAE,SAAS;CACnC,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenero market analytics tools
|
|
3
|
+
*
|
|
4
|
+
* Read-only MCP tools for Stacks ecosystem market data via the Tenero API
|
|
5
|
+
* (formerly STXTools) at https://api.tenero.io. No authentication required.
|
|
6
|
+
*
|
|
7
|
+
* Tools:
|
|
8
|
+
* - tenero_token_info — Token metadata, price, and volume
|
|
9
|
+
* - tenero_market_summary — Price history and pool liquidity for a token
|
|
10
|
+
* - tenero_market_stats — Overall market volume, netflow, and active traders
|
|
11
|
+
* - tenero_top_gainers — Top gaining tokens by 24h price change
|
|
12
|
+
* - tenero_top_losers — Top losing tokens by 24h price change
|
|
13
|
+
* - tenero_trending_pools — Trending DEX liquidity pools by 1h volume
|
|
14
|
+
* - tenero_wallet_trades — Trade history for a wallet address
|
|
15
|
+
* - tenero_wallet_holdings — Token holdings with current USD value for a wallet
|
|
16
|
+
* - tenero_whale_trades — Recent large trades above threshold value
|
|
17
|
+
* - tenero_holder_stats — Token holder distribution and concentration stats
|
|
18
|
+
* - tenero_search — Search tokens, pools, and wallets by name or address
|
|
19
|
+
*/
|
|
20
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21
|
+
export declare function registerTeneroTools(server: McpServer): void;
|
|
22
|
+
//# sourceMappingURL=tenero.tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenero.tools.d.ts","sourceRoot":"","sources":["../../src/tools/tenero.tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAiBpE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkW3D"}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenero market analytics tools
|
|
3
|
+
*
|
|
4
|
+
* Read-only MCP tools for Stacks ecosystem market data via the Tenero API
|
|
5
|
+
* (formerly STXTools) at https://api.tenero.io. No authentication required.
|
|
6
|
+
*
|
|
7
|
+
* Tools:
|
|
8
|
+
* - tenero_token_info — Token metadata, price, and volume
|
|
9
|
+
* - tenero_market_summary — Price history and pool liquidity for a token
|
|
10
|
+
* - tenero_market_stats — Overall market volume, netflow, and active traders
|
|
11
|
+
* - tenero_top_gainers — Top gaining tokens by 24h price change
|
|
12
|
+
* - tenero_top_losers — Top losing tokens by 24h price change
|
|
13
|
+
* - tenero_trending_pools — Trending DEX liquidity pools by 1h volume
|
|
14
|
+
* - tenero_wallet_trades — Trade history for a wallet address
|
|
15
|
+
* - tenero_wallet_holdings — Token holdings with current USD value for a wallet
|
|
16
|
+
* - tenero_whale_trades — Recent large trades above threshold value
|
|
17
|
+
* - tenero_holder_stats — Token holder distribution and concentration stats
|
|
18
|
+
* - tenero_search — Search tokens, pools, and wallets by name or address
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { createJsonResponse, createErrorResponse } from "../utils/index.js";
|
|
22
|
+
import { getTokenInfo, getMarketSummary, getMarketStats, getTopGainers, getTopLosers, getTrendingPools, getWalletTrades, getWalletHoldings, getWhaleTrades, getHolderStats, searchTokens, } from "../services/tenero-api.js";
|
|
23
|
+
export function registerTeneroTools(server) {
|
|
24
|
+
// Token info
|
|
25
|
+
server.registerTool("tenero_token_info", {
|
|
26
|
+
description: "Get token details including metadata, current price, market cap, and 24h volume. " +
|
|
27
|
+
"Powered by the Tenero API (api.tenero.io). No authentication required.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
contractId: z
|
|
30
|
+
.string()
|
|
31
|
+
.describe("Token contract address in format PRINCIPAL.contract-name " +
|
|
32
|
+
"(e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex)"),
|
|
33
|
+
chain: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.default("stacks")
|
|
37
|
+
.describe("Chain to query: stacks, spark, or sportsfun (default: stacks)"),
|
|
38
|
+
},
|
|
39
|
+
}, async ({ contractId, chain }) => {
|
|
40
|
+
try {
|
|
41
|
+
const data = await getTokenInfo(contractId, chain);
|
|
42
|
+
return createJsonResponse(data);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return createErrorResponse(error);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
// Market summary (per token)
|
|
49
|
+
server.registerTool("tenero_market_summary", {
|
|
50
|
+
description: "Get token market summary including price history, 24h volume, and pool liquidity. " +
|
|
51
|
+
"Returns weighted price across all pools trading this token. " +
|
|
52
|
+
"Powered by the Tenero API (api.tenero.io). No authentication required.",
|
|
53
|
+
inputSchema: {
|
|
54
|
+
contractId: z
|
|
55
|
+
.string()
|
|
56
|
+
.describe("Token contract address in format PRINCIPAL.contract-name " +
|
|
57
|
+
"(e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex)"),
|
|
58
|
+
chain: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.default("stacks")
|
|
62
|
+
.describe("Chain to query: stacks, spark, or sportsfun (default: stacks)"),
|
|
63
|
+
},
|
|
64
|
+
}, async ({ contractId, chain }) => {
|
|
65
|
+
try {
|
|
66
|
+
const data = await getMarketSummary(contractId, chain);
|
|
67
|
+
return createJsonResponse(data);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return createErrorResponse(error);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// Overall market stats
|
|
74
|
+
server.registerTool("tenero_market_stats", {
|
|
75
|
+
description: "Get overall Stacks ecosystem market statistics including total volume, " +
|
|
76
|
+
"buy/sell netflow, unique traders, and active pools. " +
|
|
77
|
+
"Returns a time series of daily stats for recent periods. " +
|
|
78
|
+
"Powered by the Tenero API (api.tenero.io). No authentication required.",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
chain: z
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.default("stacks")
|
|
84
|
+
.describe("Chain to query: stacks, spark, or sportsfun (default: stacks)"),
|
|
85
|
+
},
|
|
86
|
+
}, async ({ chain }) => {
|
|
87
|
+
try {
|
|
88
|
+
const data = await getMarketStats(chain);
|
|
89
|
+
return createJsonResponse(data);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return createErrorResponse(error);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
// Top gainers
|
|
96
|
+
server.registerTool("tenero_top_gainers", {
|
|
97
|
+
description: "List top gaining tokens by 24h price change percentage on the Stacks ecosystem. " +
|
|
98
|
+
"Useful for spotting momentum and trending assets. " +
|
|
99
|
+
"Powered by the Tenero API (api.tenero.io). No authentication required.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
limit: z
|
|
102
|
+
.number()
|
|
103
|
+
.int()
|
|
104
|
+
.min(1)
|
|
105
|
+
.max(50)
|
|
106
|
+
.optional()
|
|
107
|
+
.default(10)
|
|
108
|
+
.describe("Maximum number of tokens to return (default: 10, max: 50)"),
|
|
109
|
+
chain: z
|
|
110
|
+
.string()
|
|
111
|
+
.optional()
|
|
112
|
+
.default("stacks")
|
|
113
|
+
.describe("Chain to query: stacks, spark, or sportsfun (default: stacks)"),
|
|
114
|
+
},
|
|
115
|
+
}, async ({ limit, chain }) => {
|
|
116
|
+
try {
|
|
117
|
+
const data = await getTopGainers(limit, chain);
|
|
118
|
+
return createJsonResponse(data);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return createErrorResponse(error);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
// Top losers
|
|
125
|
+
server.registerTool("tenero_top_losers", {
|
|
126
|
+
description: "List top losing tokens by 24h price change percentage on the Stacks ecosystem. " +
|
|
127
|
+
"Useful for identifying underperforming assets or potential reversal candidates. " +
|
|
128
|
+
"Powered by the Tenero API (api.tenero.io). No authentication required.",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
limit: z
|
|
131
|
+
.number()
|
|
132
|
+
.int()
|
|
133
|
+
.min(1)
|
|
134
|
+
.max(50)
|
|
135
|
+
.optional()
|
|
136
|
+
.default(10)
|
|
137
|
+
.describe("Maximum number of tokens to return (default: 10, max: 50)"),
|
|
138
|
+
chain: z
|
|
139
|
+
.string()
|
|
140
|
+
.optional()
|
|
141
|
+
.default("stacks")
|
|
142
|
+
.describe("Chain to query: stacks, spark, or sportsfun (default: stacks)"),
|
|
143
|
+
},
|
|
144
|
+
}, async ({ limit, chain }) => {
|
|
145
|
+
try {
|
|
146
|
+
const data = await getTopLosers(limit, chain);
|
|
147
|
+
return createJsonResponse(data);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
return createErrorResponse(error);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
// Trending pools
|
|
154
|
+
server.registerTool("tenero_trending_pools", {
|
|
155
|
+
description: "List trending DEX liquidity pools by volume over the last hour. " +
|
|
156
|
+
"Includes pool platform, token pair, volume, and liquidity details. " +
|
|
157
|
+
"Powered by the Tenero API (api.tenero.io). No authentication required.",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
limit: z
|
|
160
|
+
.number()
|
|
161
|
+
.int()
|
|
162
|
+
.min(1)
|
|
163
|
+
.max(50)
|
|
164
|
+
.optional()
|
|
165
|
+
.default(10)
|
|
166
|
+
.describe("Maximum number of pools to return (default: 10, max: 50)"),
|
|
167
|
+
chain: z
|
|
168
|
+
.string()
|
|
169
|
+
.optional()
|
|
170
|
+
.default("stacks")
|
|
171
|
+
.describe("Chain to query: stacks, spark, or sportsfun (default: stacks)"),
|
|
172
|
+
},
|
|
173
|
+
}, async ({ limit, chain }) => {
|
|
174
|
+
try {
|
|
175
|
+
const data = await getTrendingPools(limit, chain);
|
|
176
|
+
return createJsonResponse(data);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
return createErrorResponse(error);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
// Wallet trades
|
|
183
|
+
server.registerTool("tenero_wallet_trades", {
|
|
184
|
+
description: "Get trade history for a Stacks wallet address. " +
|
|
185
|
+
"Returns recent buy/sell events with token, pool, and USD value details. " +
|
|
186
|
+
"Powered by the Tenero API (api.tenero.io). No authentication required.",
|
|
187
|
+
inputSchema: {
|
|
188
|
+
address: z
|
|
189
|
+
.string()
|
|
190
|
+
.describe("Stacks wallet address (SP... or SM...)"),
|
|
191
|
+
limit: z
|
|
192
|
+
.number()
|
|
193
|
+
.int()
|
|
194
|
+
.min(1)
|
|
195
|
+
.max(100)
|
|
196
|
+
.optional()
|
|
197
|
+
.default(20)
|
|
198
|
+
.describe("Maximum number of trades to return (default: 20, max: 100)"),
|
|
199
|
+
chain: z
|
|
200
|
+
.string()
|
|
201
|
+
.optional()
|
|
202
|
+
.default("stacks")
|
|
203
|
+
.describe("Chain to query: stacks, spark, or sportsfun (default: stacks)"),
|
|
204
|
+
},
|
|
205
|
+
}, async ({ address, limit, chain }) => {
|
|
206
|
+
try {
|
|
207
|
+
const data = await getWalletTrades(address, limit, chain);
|
|
208
|
+
return createJsonResponse(data);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
return createErrorResponse(error);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
// Wallet holdings
|
|
215
|
+
server.registerTool("tenero_wallet_holdings", {
|
|
216
|
+
description: "Get token holdings with current USD value for a Stacks wallet address. " +
|
|
217
|
+
"Shows portfolio composition including token balances and estimated values. " +
|
|
218
|
+
"Powered by the Tenero API (api.tenero.io). No authentication required.",
|
|
219
|
+
inputSchema: {
|
|
220
|
+
address: z
|
|
221
|
+
.string()
|
|
222
|
+
.describe("Stacks wallet address (SP... or SM...)"),
|
|
223
|
+
chain: z
|
|
224
|
+
.string()
|
|
225
|
+
.optional()
|
|
226
|
+
.default("stacks")
|
|
227
|
+
.describe("Chain to query: stacks, spark, or sportsfun (default: stacks)"),
|
|
228
|
+
},
|
|
229
|
+
}, async ({ address, chain }) => {
|
|
230
|
+
try {
|
|
231
|
+
const data = await getWalletHoldings(address, chain);
|
|
232
|
+
return createJsonResponse(data);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
return createErrorResponse(error);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
// Whale trades
|
|
239
|
+
server.registerTool("tenero_whale_trades", {
|
|
240
|
+
description: "Get recent large/whale trades above threshold value on the Stacks ecosystem. " +
|
|
241
|
+
"Useful for tracking smart money and large market movements. " +
|
|
242
|
+
"Powered by the Tenero API (api.tenero.io). No authentication required.",
|
|
243
|
+
inputSchema: {
|
|
244
|
+
limit: z
|
|
245
|
+
.number()
|
|
246
|
+
.int()
|
|
247
|
+
.min(1)
|
|
248
|
+
.max(50)
|
|
249
|
+
.optional()
|
|
250
|
+
.default(10)
|
|
251
|
+
.describe("Maximum number of whale trades to return (default: 10, max: 50)"),
|
|
252
|
+
chain: z
|
|
253
|
+
.string()
|
|
254
|
+
.optional()
|
|
255
|
+
.default("stacks")
|
|
256
|
+
.describe("Chain to query: stacks, spark, or sportsfun (default: stacks)"),
|
|
257
|
+
},
|
|
258
|
+
}, async ({ limit, chain }) => {
|
|
259
|
+
try {
|
|
260
|
+
const data = await getWhaleTrades(limit, chain);
|
|
261
|
+
return createJsonResponse(data);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
return createErrorResponse(error);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
// Holder stats
|
|
268
|
+
server.registerTool("tenero_holder_stats", {
|
|
269
|
+
description: "Get token holder distribution and concentration statistics. " +
|
|
270
|
+
"Shows total holders, top holder percentages, and Gini coefficient. " +
|
|
271
|
+
"Powered by the Tenero API (api.tenero.io). No authentication required.",
|
|
272
|
+
inputSchema: {
|
|
273
|
+
contractId: z
|
|
274
|
+
.string()
|
|
275
|
+
.describe("Token contract address in format PRINCIPAL.contract-name " +
|
|
276
|
+
"(e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex)"),
|
|
277
|
+
chain: z
|
|
278
|
+
.string()
|
|
279
|
+
.optional()
|
|
280
|
+
.default("stacks")
|
|
281
|
+
.describe("Chain to query: stacks, spark, or sportsfun (default: stacks)"),
|
|
282
|
+
},
|
|
283
|
+
}, async ({ contractId, chain }) => {
|
|
284
|
+
try {
|
|
285
|
+
const data = await getHolderStats(contractId, chain);
|
|
286
|
+
return createJsonResponse(data);
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
return createErrorResponse(error);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
// Search
|
|
293
|
+
server.registerTool("tenero_search", {
|
|
294
|
+
description: "Search tokens, pools, and wallets by name, symbol, or contract address. " +
|
|
295
|
+
"Returns matching tokens with metadata and pricing information. " +
|
|
296
|
+
"Powered by the Tenero API (api.tenero.io). No authentication required.",
|
|
297
|
+
inputSchema: {
|
|
298
|
+
query: z
|
|
299
|
+
.string()
|
|
300
|
+
.min(1)
|
|
301
|
+
.describe("Search query: token name, symbol, or contract address"),
|
|
302
|
+
chain: z
|
|
303
|
+
.string()
|
|
304
|
+
.optional()
|
|
305
|
+
.default("stacks")
|
|
306
|
+
.describe("Chain to query: stacks, spark, or sportsfun (default: stacks)"),
|
|
307
|
+
},
|
|
308
|
+
}, async ({ query, chain }) => {
|
|
309
|
+
try {
|
|
310
|
+
const data = await searchTokens(query, chain);
|
|
311
|
+
return createJsonResponse(data);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
return createErrorResponse(error);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=tenero.tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenero.tools.js","sourceRoot":"","sources":["../../src/tools/tenero.tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,YAAY,GACb,MAAM,2BAA2B,CAAC;AAEnC,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,aAAa;IACb,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,WAAW,EACT,mFAAmF;YACnF,wEAAwE;QAC1E,WAAW,EAAE;YACX,UAAU,EAAE,CAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,CACP,2DAA2D;gBACzD,6DAA6D,CAChE;YACH,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE;QAC9B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;YACnD,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,6BAA6B;IAC7B,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,WAAW,EACT,oFAAoF;YACpF,8DAA8D;YAC9D,wEAAwE;QAC1E,WAAW,EAAE;YACX,UAAU,EAAE,CAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,CACP,2DAA2D;gBACzD,6DAA6D,CAChE;YACH,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE;QAC9B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;YACvD,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,uBAAuB;IACvB,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,WAAW,EACT,yEAAyE;YACzE,sDAAsD;YACtD,2DAA2D;YAC3D,wEAAwE;QAC1E,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QAClB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,CAAC;YACzC,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,cAAc;IACd,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,WAAW,EACT,kFAAkF;YAClF,oDAAoD;YACpD,wEAAwE;QAC1E,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,EAAE,CAAC;iBACP,QAAQ,EAAE;iBACV,OAAO,CAAC,EAAE,CAAC;iBACX,QAAQ,CAAC,2DAA2D,CAAC;YACxE,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAC/C,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,aAAa;IACb,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,WAAW,EACT,iFAAiF;YACjF,kFAAkF;YAClF,wEAAwE;QAC1E,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,EAAE,CAAC;iBACP,QAAQ,EAAE;iBACV,OAAO,CAAC,EAAE,CAAC;iBACX,QAAQ,CAAC,2DAA2D,CAAC;YACxE,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAC9C,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,iBAAiB;IACjB,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,WAAW,EACT,kEAAkE;YAClE,qEAAqE;YACrE,wEAAwE;QAC1E,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,EAAE,CAAC;iBACP,QAAQ,EAAE;iBACV,OAAO,CAAC,EAAE,CAAC;iBACX,QAAQ,CAAC,0DAA0D,CAAC;YACvE,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAClD,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,gBAAgB;IAChB,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,WAAW,EACT,iDAAiD;YACjD,0EAA0E;YAC1E,wEAAwE;QAC1E,WAAW,EAAE;YACX,OAAO,EAAE,CAAC;iBACP,MAAM,EAAE;iBACR,QAAQ,CAAC,wCAAwC,CAAC;YACrD,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,GAAG,CAAC;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,EAAE,CAAC;iBACX,QAAQ,CAAC,4DAA4D,CAAC;YACzE,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;QAClC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YAC1D,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,kBAAkB;IAClB,MAAM,CAAC,YAAY,CACjB,wBAAwB,EACxB;QACE,WAAW,EACT,yEAAyE;YACzE,6EAA6E;YAC7E,wEAAwE;QAC1E,WAAW,EAAE;YACX,OAAO,EAAE,CAAC;iBACP,MAAM,EAAE;iBACR,QAAQ,CAAC,wCAAwC,CAAC;YACrD,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;QAC3B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACrD,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,eAAe;IACf,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,WAAW,EACT,+EAA+E;YAC/E,8DAA8D;YAC9D,wEAAwE;QAC1E,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,EAAE,CAAC;iBACP,QAAQ,EAAE;iBACV,OAAO,CAAC,EAAE,CAAC;iBACX,QAAQ,CAAC,iEAAiE,CAAC;YAC9E,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAChD,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,eAAe;IACf,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,WAAW,EACT,8DAA8D;YAC9D,qEAAqE;YACrE,wEAAwE;QAC1E,WAAW,EAAE;YACX,UAAU,EAAE,CAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,CACP,2DAA2D;gBACzD,6DAA6D,CAChE;YACH,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE;QAC9B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;YACrD,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,SAAS;IACT,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,WAAW,EACT,0EAA0E;YAC1E,iEAAiE;YACjE,wEAAwE;QAC1E,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CAAC,uDAAuD,CAAC;YACpE,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,QAAQ,CAAC;iBACjB,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAC9C,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* diagnostic information when send failures occur.
|
|
6
6
|
*/
|
|
7
7
|
import type { Network } from "../config/networks.js";
|
|
8
|
+
export interface StuckTransaction {
|
|
9
|
+
txid: string;
|
|
10
|
+
nonce: number;
|
|
11
|
+
pendingSeconds: number;
|
|
12
|
+
}
|
|
8
13
|
export interface RelayHealthStatus {
|
|
9
14
|
healthy: boolean;
|
|
10
15
|
network: Network;
|
|
@@ -18,7 +23,10 @@ export interface RelayHealthStatus {
|
|
|
18
23
|
mempoolNonces: number[];
|
|
19
24
|
hasGaps: boolean;
|
|
20
25
|
gapCount: number;
|
|
26
|
+
mempoolDesync: boolean;
|
|
27
|
+
desyncGap: number;
|
|
21
28
|
};
|
|
29
|
+
stuckTransactions?: StuckTransaction[];
|
|
22
30
|
issues?: string[];
|
|
23
31
|
}
|
|
24
32
|
/**
|
|
@@ -29,6 +37,26 @@ export declare function checkRelayHealth(network: Network): Promise<RelayHealthS
|
|
|
29
37
|
* Format relay health status as a human-readable string
|
|
30
38
|
*/
|
|
31
39
|
export declare function formatRelayHealthStatus(status: RelayHealthStatus): string;
|
|
40
|
+
export interface RelayRecoveryResult {
|
|
41
|
+
supported: boolean;
|
|
42
|
+
message?: string;
|
|
43
|
+
result?: unknown;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Attempt RBF (replace-by-fee) on stuck transactions via the relay API.
|
|
47
|
+
* If txids is provided, only those transactions are bumped; otherwise the relay
|
|
48
|
+
* bumps all stuck transactions it knows about.
|
|
49
|
+
*
|
|
50
|
+
* Gracefully returns { supported: false } if the relay returns 404 or 501.
|
|
51
|
+
*/
|
|
52
|
+
export declare function attemptRbf(network: Network, txids?: string[], apiKey?: string): Promise<RelayRecoveryResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Attempt to fill nonce gaps on the relay by having it submit placeholder transactions.
|
|
55
|
+
* If nonces is provided, only those gaps are filled; otherwise the relay fills all detected gaps.
|
|
56
|
+
*
|
|
57
|
+
* Gracefully returns { supported: false } if the relay returns 404 or 501.
|
|
58
|
+
*/
|
|
59
|
+
export declare function attemptFillGaps(network: Network, nonces?: number[], apiKey?: string): Promise<RelayRecoveryResult>;
|
|
32
60
|
/**
|
|
33
61
|
* Wait with exponential backoff when relay nonce issues are detected
|
|
34
62
|
* Returns recommended wait time in milliseconds
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relay-health.d.ts","sourceRoot":"","sources":["../../src/utils/relay-health.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAGrD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE;QACZ,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,EAAE,CAAC;QACxB,aAAa,EAAE,MAAM,EAAE,CAAC;QACxB,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"relay-health.d.ts","sourceRoot":"","sources":["../../src/utils/relay-health.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAGrD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE;QACZ,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,EAAE,CAAC;QACxB,aAAa,EAAE,MAAM,EAAE,CAAC;QACxB,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,OAAO,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAUD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA4HnF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAuDzE;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAiDlH;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAiDxH;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,MAAM,CASnF"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Proactively checks the sponsor relay for nonce gaps and provides
|
|
5
5
|
* diagnostic information when send failures occur.
|
|
6
6
|
*/
|
|
7
|
-
import { getSponsorRelayUrl } from "../config/sponsor.js";
|
|
7
|
+
import { getSponsorRelayUrl, getSponsorApiKey } from "../config/sponsor.js";
|
|
8
8
|
import { getHiroApi } from "../services/hiro-api.js";
|
|
9
9
|
/**
|
|
10
10
|
* Known sponsor addresses for each network.
|
|
@@ -53,27 +53,65 @@ export async function checkRelayHealth(network) {
|
|
|
53
53
|
const nonceInfo = await hiroApi.getNonceInfo(sponsorAddress);
|
|
54
54
|
const hasGaps = nonceInfo.detected_missing_nonces.length > 0;
|
|
55
55
|
const gapCount = nonceInfo.detected_missing_nonces.length;
|
|
56
|
+
// Mempool desync: sponsor has submitted far more txs than have been confirmed
|
|
57
|
+
const lastExecuted = nonceInfo.last_executed_tx_nonce || 0;
|
|
58
|
+
const lastMempool = nonceInfo.last_mempool_tx_nonce;
|
|
59
|
+
const desyncGap = lastMempool !== null ? lastMempool - lastExecuted : 0;
|
|
60
|
+
const mempoolDesync = desyncGap > 5;
|
|
56
61
|
if (hasGaps) {
|
|
57
62
|
issues.push(`Sponsor has ${gapCount} missing nonce(s): ${nonceInfo.detected_missing_nonces.slice(0, 5).join(", ")}${gapCount > 5 ? "..." : ""}`);
|
|
58
63
|
}
|
|
59
|
-
if (
|
|
64
|
+
if (mempoolDesync) {
|
|
65
|
+
issues.push(`Mempool desync detected: sponsor nonce ${lastExecuted} (executed) vs ${lastMempool} (mempool), gap of ${desyncGap}`);
|
|
66
|
+
}
|
|
67
|
+
else if (nonceInfo.detected_mempool_nonces.length > 10) {
|
|
60
68
|
issues.push(`Sponsor has ${nonceInfo.detected_mempool_nonces.length} transactions stuck in mempool`);
|
|
61
69
|
}
|
|
62
70
|
const nonceStatus = {
|
|
63
|
-
lastExecuted
|
|
64
|
-
lastMempool
|
|
71
|
+
lastExecuted,
|
|
72
|
+
lastMempool,
|
|
65
73
|
possibleNext: nonceInfo.possible_next_nonce,
|
|
66
74
|
missingNonces: nonceInfo.detected_missing_nonces,
|
|
67
75
|
mempoolNonces: nonceInfo.detected_mempool_nonces,
|
|
68
76
|
hasGaps,
|
|
69
77
|
gapCount,
|
|
78
|
+
mempoolDesync,
|
|
79
|
+
desyncGap,
|
|
70
80
|
};
|
|
81
|
+
// Fetch stuck transactions from mempool for actionable diagnostics
|
|
82
|
+
let stuckTransactions;
|
|
83
|
+
try {
|
|
84
|
+
const mempoolRes = await hiroApi.getMempoolTransactions({
|
|
85
|
+
sender_address: sponsorAddress,
|
|
86
|
+
limit: 50,
|
|
87
|
+
});
|
|
88
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
89
|
+
const stuck = mempoolRes.results
|
|
90
|
+
.filter((tx) => {
|
|
91
|
+
const pendingSeconds = nowSeconds - tx.receipt_time;
|
|
92
|
+
return pendingSeconds > 60;
|
|
93
|
+
})
|
|
94
|
+
.map((tx) => ({
|
|
95
|
+
txid: tx.tx_id,
|
|
96
|
+
nonce: tx.nonce,
|
|
97
|
+
pendingSeconds: nowSeconds - tx.receipt_time,
|
|
98
|
+
}))
|
|
99
|
+
.sort((a, b) => b.pendingSeconds - a.pendingSeconds)
|
|
100
|
+
.slice(0, 10);
|
|
101
|
+
if (stuck.length > 0) {
|
|
102
|
+
stuckTransactions = stuck;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Non-fatal: stuck-tx fetch is best-effort
|
|
107
|
+
}
|
|
71
108
|
return {
|
|
72
109
|
healthy: issues.length === 0,
|
|
73
110
|
network,
|
|
74
111
|
version,
|
|
75
112
|
sponsorAddress,
|
|
76
113
|
nonceStatus,
|
|
114
|
+
stuckTransactions,
|
|
77
115
|
issues: issues.length > 0 ? issues : undefined,
|
|
78
116
|
};
|
|
79
117
|
}
|
|
@@ -107,15 +145,28 @@ export function formatRelayHealthStatus(status) {
|
|
|
107
145
|
lines.push(` Last mempool: ${ns.lastMempool ?? "none"}`);
|
|
108
146
|
lines.push(` Next nonce: ${ns.possibleNext}`);
|
|
109
147
|
if (ns.hasGaps) {
|
|
110
|
-
lines.push(`
|
|
148
|
+
lines.push(` GAPS Missing nonces (${ns.gapCount}): ${ns.missingNonces.slice(0, 10).join(", ")}${ns.gapCount > 10 ? "..." : ""}`);
|
|
111
149
|
}
|
|
112
150
|
else {
|
|
113
|
-
lines.push("
|
|
151
|
+
lines.push(" OK No nonce gaps");
|
|
152
|
+
}
|
|
153
|
+
if (ns.mempoolDesync) {
|
|
154
|
+
lines.push(` DESYNC Mempool desync: executed=${ns.lastExecuted}, mempool=${ns.lastMempool ?? "none"}, gap=${ns.desyncGap}`);
|
|
114
155
|
}
|
|
115
156
|
if (ns.mempoolNonces.length > 0) {
|
|
116
|
-
lines.push(`
|
|
157
|
+
lines.push(` WARN Mempool nonces (${ns.mempoolNonces.length}): ${ns.mempoolNonces.slice(0, 10).join(", ")}${ns.mempoolNonces.length > 10 ? "..." : ""}`);
|
|
117
158
|
}
|
|
118
159
|
}
|
|
160
|
+
if (status.stuckTransactions && status.stuckTransactions.length > 0) {
|
|
161
|
+
lines.push("");
|
|
162
|
+
lines.push("Stuck Transactions:");
|
|
163
|
+
status.stuckTransactions.forEach((tx) => {
|
|
164
|
+
const minutes = Math.floor(tx.pendingSeconds / 60);
|
|
165
|
+
const seconds = tx.pendingSeconds % 60;
|
|
166
|
+
const duration = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
167
|
+
lines.push(` nonce=${tx.nonce} pending=${duration} txid=${tx.txid}`);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
119
170
|
if (status.issues && status.issues.length > 0) {
|
|
120
171
|
lines.push("");
|
|
121
172
|
lines.push("Issues:");
|
|
@@ -123,6 +174,105 @@ export function formatRelayHealthStatus(status) {
|
|
|
123
174
|
}
|
|
124
175
|
return lines.join("\n");
|
|
125
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Attempt RBF (replace-by-fee) on stuck transactions via the relay API.
|
|
179
|
+
* If txids is provided, only those transactions are bumped; otherwise the relay
|
|
180
|
+
* bumps all stuck transactions it knows about.
|
|
181
|
+
*
|
|
182
|
+
* Gracefully returns { supported: false } if the relay returns 404 or 501.
|
|
183
|
+
*/
|
|
184
|
+
export async function attemptRbf(network, txids, apiKey) {
|
|
185
|
+
const relayUrl = getSponsorRelayUrl(network);
|
|
186
|
+
const resolvedKey = apiKey || getSponsorApiKey();
|
|
187
|
+
if (!resolvedKey) {
|
|
188
|
+
return {
|
|
189
|
+
supported: true,
|
|
190
|
+
message: "No sponsor API key available. Set SPONSOR_API_KEY env var or use a wallet with sponsorApiKey configured.",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const headers = {
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
"Authorization": `Bearer ${resolvedKey}`,
|
|
196
|
+
};
|
|
197
|
+
const body = {};
|
|
198
|
+
if (txids && txids.length > 0) {
|
|
199
|
+
body.txids = txids;
|
|
200
|
+
}
|
|
201
|
+
const controller = new AbortController();
|
|
202
|
+
const timeout = setTimeout(() => controller.abort(), 30000);
|
|
203
|
+
try {
|
|
204
|
+
const res = await fetch(`${relayUrl}/recovery/rbf`, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers,
|
|
207
|
+
body: JSON.stringify(body),
|
|
208
|
+
signal: controller.signal,
|
|
209
|
+
});
|
|
210
|
+
if (res.status === 404 || res.status === 501) {
|
|
211
|
+
return {
|
|
212
|
+
supported: false,
|
|
213
|
+
message: "Relay does not support RBF recovery yet. Share stuck txids with the AIBTC team for manual recovery.",
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (!res.ok) {
|
|
217
|
+
const text = await res.text();
|
|
218
|
+
throw new Error(`Relay RBF failed: HTTP ${res.status} — ${text}`);
|
|
219
|
+
}
|
|
220
|
+
const result = await res.json();
|
|
221
|
+
return { supported: true, result };
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
clearTimeout(timeout);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Attempt to fill nonce gaps on the relay by having it submit placeholder transactions.
|
|
229
|
+
* If nonces is provided, only those gaps are filled; otherwise the relay fills all detected gaps.
|
|
230
|
+
*
|
|
231
|
+
* Gracefully returns { supported: false } if the relay returns 404 or 501.
|
|
232
|
+
*/
|
|
233
|
+
export async function attemptFillGaps(network, nonces, apiKey) {
|
|
234
|
+
const relayUrl = getSponsorRelayUrl(network);
|
|
235
|
+
const resolvedKey = apiKey || getSponsorApiKey();
|
|
236
|
+
if (!resolvedKey) {
|
|
237
|
+
return {
|
|
238
|
+
supported: true,
|
|
239
|
+
message: "No sponsor API key available. Set SPONSOR_API_KEY env var or use a wallet with sponsorApiKey configured.",
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const headers = {
|
|
243
|
+
"Content-Type": "application/json",
|
|
244
|
+
"Authorization": `Bearer ${resolvedKey}`,
|
|
245
|
+
};
|
|
246
|
+
const body = {};
|
|
247
|
+
if (nonces && nonces.length > 0) {
|
|
248
|
+
body.nonces = nonces;
|
|
249
|
+
}
|
|
250
|
+
const controller = new AbortController();
|
|
251
|
+
const timeout = setTimeout(() => controller.abort(), 30000);
|
|
252
|
+
try {
|
|
253
|
+
const res = await fetch(`${relayUrl}/recovery/fill-gaps`, {
|
|
254
|
+
method: "POST",
|
|
255
|
+
headers,
|
|
256
|
+
body: JSON.stringify(body),
|
|
257
|
+
signal: controller.signal,
|
|
258
|
+
});
|
|
259
|
+
if (res.status === 404 || res.status === 501) {
|
|
260
|
+
return {
|
|
261
|
+
supported: false,
|
|
262
|
+
message: "Relay does not support nonce gap-fill recovery yet. Share missing nonces with the AIBTC team for manual recovery.",
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (!res.ok) {
|
|
266
|
+
const text = await res.text();
|
|
267
|
+
throw new Error(`Relay gap-fill failed: HTTP ${res.status} — ${text}`);
|
|
268
|
+
}
|
|
269
|
+
const result = await res.json();
|
|
270
|
+
return { supported: true, result };
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
clearTimeout(timeout);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
126
276
|
/**
|
|
127
277
|
* Wait with exponential backoff when relay nonce issues are detected
|
|
128
278
|
* Returns recommended wait time in milliseconds
|