hchain-mcp 1.0.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/.env.example +5 -0
- package/AGENT-MCP-RULES.md +248 -0
- package/OnchainOS-API/345/257/271/346/216/245/350/247/204/350/214/203.md +329 -0
- package/README.md +106 -0
- package/dist/adapters/onchainos-ws.d.ts +31 -0
- package/dist/adapters/onchainos-ws.js +103 -0
- package/dist/adapters/onchainos.d.ts +343 -0
- package/dist/adapters/onchainos.js +275 -0
- package/dist/adapters/shared.d.ts +23 -0
- package/dist/adapters/shared.js +107 -0
- package/dist/http.d.ts +9 -0
- package/dist/http.js +124 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +54 -0
- package/dist/tools/balance.d.ts +4 -0
- package/dist/tools/balance.js +69 -0
- package/dist/tools/defi.d.ts +4 -0
- package/dist/tools/defi.js +268 -0
- package/dist/tools/gateway.d.ts +4 -0
- package/dist/tools/gateway.js +107 -0
- package/dist/tools/intent.d.ts +4 -0
- package/dist/tools/intent.js +111 -0
- package/dist/tools/market.d.ts +4 -0
- package/dist/tools/market.js +612 -0
- package/dist/tools/payments.d.ts +4 -0
- package/dist/tools/payments.js +100 -0
- package/dist/tools/skills.d.ts +4 -0
- package/dist/tools/skills.js +464 -0
- package/dist/tools/trade.d.ts +4 -0
- package/dist/tools/trade.js +195 -0
- package/dist/tools/txhistory.d.ts +4 -0
- package/dist/tools/txhistory.js +49 -0
- package/dist/tools/ws.d.ts +4 -0
- package/dist/tools/ws.js +64 -0
- package/docs/DEFI.md +65 -0
- package/docs/GATEWAY.md +45 -0
- package/docs/MARKET.md +109 -0
- package/docs/PAYMENTS.md +83 -0
- package/docs/TRADE.md +103 -0
- package/package.json +25 -0
- package/src/adapters/onchainos-ws.ts +126 -0
- package/src/adapters/onchainos.ts +488 -0
- package/src/adapters/shared.ts +100 -0
- package/src/http.ts +132 -0
- package/src/index.ts +57 -0
- package/src/tools/balance.ts +67 -0
- package/src/tools/defi.ts +224 -0
- package/src/tools/gateway.ts +115 -0
- package/src/tools/intent.ts +106 -0
- package/src/tools/market.ts +543 -0
- package/src/tools/payments.ts +105 -0
- package/src/tools/skills.ts +489 -0
- package/src/tools/trade.ts +197 -0
- package/src/tools/txhistory.ts +51 -0
- package/src/tools/ws.ts +72 -0
- package/tsconfig.json +18 -0
package/src/http.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* h-mcp Server — HTTP 传输层
|
|
3
|
+
* 规范: AGENT-MCP-RULES.md §12 (传输层透明 + 无状态)
|
|
4
|
+
* 使用 StreamableHTTPServerTransport (SDK 推荐), 废弃的 SSE transport 已跳过
|
|
5
|
+
*
|
|
6
|
+
* 使用原生 node:http 而非 Express, 避免 Express 5 与 @hono/node-server 兼容问题
|
|
7
|
+
*/
|
|
8
|
+
import "dotenv/config";
|
|
9
|
+
import { createServer } from "node:http";
|
|
10
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
11
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
13
|
+
import type { Auth } from "./adapters/shared.js";
|
|
14
|
+
import { registerBalanceTools } from "./tools/balance.js";
|
|
15
|
+
import { registerGatewayTools } from "./tools/gateway.js";
|
|
16
|
+
import { registerTxHistoryTools } from "./tools/txhistory.js";
|
|
17
|
+
import { registerDefiTools } from "./tools/defi.js";
|
|
18
|
+
import { registerPaymentsTools } from "./tools/payments.js";
|
|
19
|
+
import { registerTradeTools } from "./tools/trade.js";
|
|
20
|
+
import { registerIntentTools } from "./tools/intent.js";
|
|
21
|
+
import { registerMarketTools } from "./tools/market.js";
|
|
22
|
+
import { registerWsTools } from "./tools/ws.js";
|
|
23
|
+
import { registerSkillTools } from "./tools/skills.js";
|
|
24
|
+
|
|
25
|
+
function resolveAuth(): Auth | null {
|
|
26
|
+
const k = process.env.OKX_API_KEY, s = process.env.OKX_SECRET_KEY, p = process.env.OKX_PASSPHRASE;
|
|
27
|
+
if (k && s && p) return { apiKey: k, secret: s, passphrase: p };
|
|
28
|
+
const a = process.argv.slice(2), g = (f: string) => { const i = a.indexOf(f); return i >= 0 ? a[i + 1] : undefined; };
|
|
29
|
+
const ka = g("--okx-api-key") ?? g("-k"), sa = g("--okx-secret") ?? g("-s"), pa = g("--okx-passphrase") ?? g("-p");
|
|
30
|
+
if (ka && sa && pa) return { apiKey: ka, secret: sa, passphrase: pa };
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 简陋 body parser — 读 JSON body 到 req.body */
|
|
35
|
+
function jsonBody(req: IncomingMessage): Promise<unknown> {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const chunks: Buffer[] = [];
|
|
38
|
+
req.on("data", c => chunks.push(c));
|
|
39
|
+
req.on("end", () => {
|
|
40
|
+
const raw = Buffer.concat(chunks).toString();
|
|
41
|
+
try { resolve(raw ? JSON.parse(raw) : undefined); }
|
|
42
|
+
catch { reject(new Error("Invalid JSON body")); }
|
|
43
|
+
});
|
|
44
|
+
req.on("error", reject);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function main() {
|
|
49
|
+
const auth = resolveAuth();
|
|
50
|
+
if (!auth) {
|
|
51
|
+
console.error("[h-mcp] 未配置 API Key。设置 OKX_API_KEY / OKX_SECRET_KEY / OKX_PASSPHRASE");
|
|
52
|
+
console.error("[h-mcp] 获取: https://web3.okx.com/onchainos/dev-portal");
|
|
53
|
+
} else {
|
|
54
|
+
console.error("[h-mcp] Auth 已配置");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const host = process.env.HOST ?? "127.0.0.1";
|
|
58
|
+
const port = parseInt(process.env.PORT ?? "3000", 10);
|
|
59
|
+
|
|
60
|
+
// 1. 创建 HTTP transport (有状态模式, 自动生成 sessionId)
|
|
61
|
+
const transport = new StreamableHTTPServerTransport({
|
|
62
|
+
sessionIdGenerator: () => crypto.randomUUID(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// 2. MCP Server + 全部工具注册
|
|
66
|
+
const server = new McpServer({ name: "hchain-mcp", version: "1.0.0" });
|
|
67
|
+
|
|
68
|
+
registerBalanceTools(server, auth);
|
|
69
|
+
registerGatewayTools(server, auth);
|
|
70
|
+
registerTxHistoryTools(server, auth);
|
|
71
|
+
registerDefiTools(server, auth);
|
|
72
|
+
registerPaymentsTools(server, auth);
|
|
73
|
+
registerTradeTools(server, auth);
|
|
74
|
+
registerIntentTools(server, auth);
|
|
75
|
+
registerMarketTools(server, auth);
|
|
76
|
+
registerWsTools(server, auth);
|
|
77
|
+
registerSkillTools(server, auth);
|
|
78
|
+
|
|
79
|
+
// 3. 连接 server 到 HTTP transport
|
|
80
|
+
await server.connect(transport);
|
|
81
|
+
|
|
82
|
+
// 4. 原生 HTTP server
|
|
83
|
+
const httpServer = createServer(async (req, res) => {
|
|
84
|
+
// CORS
|
|
85
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
86
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
87
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, MCP-Protocol-Version");
|
|
88
|
+
|
|
89
|
+
if (req.method === "OPTIONS") {
|
|
90
|
+
res.writeHead(204);
|
|
91
|
+
res.end();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 健康检查
|
|
96
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
97
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
98
|
+
res.end(JSON.stringify({ status: "ok", tsIso: new Date().toISOString() }));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// MCP 端点
|
|
103
|
+
if (req.url === "/mcp" || req.url?.startsWith("/mcp")) {
|
|
104
|
+
try {
|
|
105
|
+
let body: unknown;
|
|
106
|
+
if (req.method === "POST") {
|
|
107
|
+
body = await jsonBody(req);
|
|
108
|
+
}
|
|
109
|
+
await transport.handleRequest(req, res, body);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.error("[h-mcp] handleRequest error:", e);
|
|
112
|
+
if (!res.headersSent) {
|
|
113
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
114
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 404
|
|
121
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
122
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
httpServer.listen(port, host, () => {
|
|
126
|
+
console.error(`[h-mcp] HTTP MCP Server 已启动 → http://${host}:${port}`);
|
|
127
|
+
console.error(`[h-mcp] MCP endpoint: POST http://${host}:${port}/mcp`);
|
|
128
|
+
console.error(`[h-mcp] Health check: GET http://${host}:${port}/health`);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
main().catch(e => { console.error("[h-mcp] 启动失败:", e); process.exit(1); });
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* h-mcp Server — stdio 传输
|
|
3
|
+
* 规范: OnchainOS-API对接规范.md §五
|
|
4
|
+
*/
|
|
5
|
+
import "dotenv/config";
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import type { Auth } from "./adapters/shared.js";
|
|
9
|
+
import { registerBalanceTools } from "./tools/balance.js";
|
|
10
|
+
import { registerGatewayTools } from "./tools/gateway.js";
|
|
11
|
+
import { registerTxHistoryTools } from "./tools/txhistory.js";
|
|
12
|
+
import { registerDefiTools } from "./tools/defi.js";
|
|
13
|
+
import { registerPaymentsTools } from "./tools/payments.js";
|
|
14
|
+
import { registerTradeTools } from "./tools/trade.js";
|
|
15
|
+
import { registerIntentTools } from "./tools/intent.js";
|
|
16
|
+
import { registerMarketTools } from "./tools/market.js";
|
|
17
|
+
import { registerWsTools } from "./tools/ws.js";
|
|
18
|
+
import { registerSkillTools } from "./tools/skills.js";
|
|
19
|
+
|
|
20
|
+
function resolveAuth(): Auth | null {
|
|
21
|
+
const k = process.env.OKX_API_KEY, s = process.env.OKX_SECRET_KEY, p = process.env.OKX_PASSPHRASE;
|
|
22
|
+
if (k && s && p) return { apiKey: k, secret: s, passphrase: p };
|
|
23
|
+
const a = process.argv.slice(2), g = (f: string) => { const i = a.indexOf(f); return i >= 0 ? a[i + 1] : undefined; };
|
|
24
|
+
const ka = g("--okx-api-key") ?? g("-k"), sa = g("--okx-secret") ?? g("-s"), pa = g("--okx-passphrase") ?? g("-p");
|
|
25
|
+
if (ka && sa && pa) return { apiKey: ka, secret: sa, passphrase: pa };
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
const auth = resolveAuth();
|
|
31
|
+
if (!auth) {
|
|
32
|
+
console.error("[h-mcp] 未配置 API Key。设置 OKX_API_KEY / OKX_SECRET_KEY / OKX_PASSPHRASE");
|
|
33
|
+
console.error("[h-mcp] 获取: https://web3.okx.com/onchainos/dev-portal");
|
|
34
|
+
} else {
|
|
35
|
+
console.error("[h-mcp] Auth 已配置");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const server = new McpServer({ name: "hchain-mcp", version: "1.0.0" });
|
|
39
|
+
|
|
40
|
+
// 逐模块注册工具 (按官方文档对接)
|
|
41
|
+
registerBalanceTools(server, auth);
|
|
42
|
+
registerGatewayTools(server, auth);
|
|
43
|
+
registerTxHistoryTools(server, auth);
|
|
44
|
+
registerDefiTools(server, auth);
|
|
45
|
+
registerPaymentsTools(server, auth);
|
|
46
|
+
registerTradeTools(server, auth);
|
|
47
|
+
registerIntentTools(server, auth);
|
|
48
|
+
registerMarketTools(server, auth);
|
|
49
|
+
registerWsTools(server, auth);
|
|
50
|
+
registerSkillTools(server, auth);
|
|
51
|
+
|
|
52
|
+
const transport = new StdioServerTransport();
|
|
53
|
+
await server.connect(transport);
|
|
54
|
+
console.error("[h-mcp] 就绪");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
main().catch(e => { console.error("[h-mcp] 启动失败:", e); process.exit(1); });
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Balance 模块 — CAT:[链上-账户]
|
|
3
|
+
* 按官方文档逐端点对接
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { balanceApi } from "../adapters/onchainos.js";
|
|
8
|
+
import { toResult, toError, AUTH_REQUIRED } from "../adapters/shared.js";
|
|
9
|
+
import type { Auth } from "../adapters/shared.js";
|
|
10
|
+
|
|
11
|
+
export function registerBalanceTools(server: McpServer, auth: Auth | null): void {
|
|
12
|
+
|
|
13
|
+
server.tool("onchainos_balance_supported_chain",
|
|
14
|
+
"CAT:[链上-账户] | ## 功能: 获取余额 API 支持的链, 返回 chainIndex(字符串)/name/shortName/logoUrl\n## 场景: Agent 在查余额前先调此工具确认目标链的 chainIndex 值\n## 关键词: 余额, 链列表, chainIndex, balance\n## 参数: 无\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 微小 ~2KB\n## 关联: 本工具 -> onchainos_balance_total_value(总估值) / onchainos_balance_all_tokens(资产明细)",
|
|
15
|
+
{}, { readOnlyHint: true },
|
|
16
|
+
async () => { if(!auth) return AUTH_REQUIRED("READ"); try { return toResult(await balanceApi.supportedChain(auth)); } catch(e) { return toError(e); } },
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
server.tool("onchainos_balance_total_value",
|
|
20
|
+
"CAT:[链上-账户] | ## 功能: 获取地址的总资产 USD 估值(代币+DeFi)\n## 场景: Agent 拿到钱包地址后第一步 - 快速了解总资产\n## 关键词: 总资产, total value, USD, 估值\n## 参数:\n## - address: 钱包地址(必填)\n## - chains: 链索引逗号分隔, 最多50个(必填)\n## - assetType: 资产类型(可选)\n## - excludeRiskToken: 是否过滤风险代币(可选, 默认true)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 微小 ~1KB\n## 关联: onchainos_balance_supported_chain -> 本工具总览 -> onchainos_balance_all_tokens(明细)",
|
|
21
|
+
{
|
|
22
|
+
address: z.string().describe("钱包地址。Agent 从用户输入或上下文获取"),
|
|
23
|
+
chains: z.string().describe("链索引, 逗号分隔, 最多50个。如 '1'=ETH '56'=BSC '501'=Solana。从 onchainos_balance_supported_chain 获取 chainIndex"),
|
|
24
|
+
assetType: z.enum(["0","1","2"]).optional().describe("资产类型: 不传默认所有, '0'=代币+DeFi, '1'=仅代币, '2'=仅DeFi"),
|
|
25
|
+
excludeRiskToken: z.boolean().optional().describe("过滤风险/貔貅代币。不传默认过滤, false 传不过滤"),
|
|
26
|
+
},
|
|
27
|
+
{ readOnlyHint: true },
|
|
28
|
+
async ({ address, chains, assetType, excludeRiskToken }) => {
|
|
29
|
+
if(!auth) return AUTH_REQUIRED("READ");
|
|
30
|
+
try { return toResult(await balanceApi.totalValue(auth, address, chains, assetType, excludeRiskToken)); } catch(e) { return toError(e); }
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
server.tool("onchainos_balance_all_tokens",
|
|
35
|
+
"CAT:[链上-账户] | ## 功能: 获取地址的代币持仓明细, 返回 symbol/balance/tokenPrice/isRiskToken/chainIndex\n## 场景: 用户问持仓有什么币时调用, 返回所有代币余额+美元价值\n## 关键词: 持仓, 代币余额, token balance, 资产明细\n## 参数:\n## - address: 钱包地址(必填)\n## - chains: 链索引逗号分隔, 最多50个(必填)\n## - excludeRiskToken: 风险代币过滤(可选)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 大 ~100KB\n## 关联: onchainos_balance_total_value(总览) -> 本工具明细 -> onchainos_balance_specific_token(精确查)",
|
|
36
|
+
{
|
|
37
|
+
address: z.string().describe("钱包地址。Agent 从用户输入或上下文获取"),
|
|
38
|
+
chains: z.string().describe("链索引, 逗号分隔, 最多50个。如 '1'=ETH '56'=BSC。从 onchainos_balance_supported_chain 获取"),
|
|
39
|
+
excludeRiskToken: z.enum(["0","1"]).optional().describe("'0'=过滤风险/貔貅代币(默认), '1'=不过滤"),
|
|
40
|
+
},
|
|
41
|
+
{ readOnlyHint: true },
|
|
42
|
+
async ({ address, chains, excludeRiskToken }) => {
|
|
43
|
+
if(!auth) return AUTH_REQUIRED("READ");
|
|
44
|
+
try { return toResult(await balanceApi.allTokenBalances(auth, address, chains, excludeRiskToken)); } catch(e) { return toError(e); }
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
server.tool("onchainos_balance_specific_token",
|
|
49
|
+
"CAT:[链上-账户] | ## 功能: 批量查询指定代币余额(最多20个), 每个代币可指定不同链\n## 场景: 查一个或多个特定代币的余额。tokenContractAddress 传空字符串查主链币(ETH/SOL等)\n## 关键词: 代币余额, specific token, 批量, 精确, tokenContractAddress\n## 参数:\n## - address: 钱包地址(必填)\n## - tokens: JSON数组 [{\"chainIndex\":\"1\",\"tokenContractAddress\":\"0x...\"},...], 最多20个\n## - excludeRiskToken: 风险代币过滤(可选)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 微小 ~5KB\n## 关联: onchainos_token_search 拿地址 -> 构造 tokens 数组 -> 本工具 -> 返回 symbol/balance/tokenPrice/isRiskToken",
|
|
50
|
+
{
|
|
51
|
+
address: z.string().describe("钱包地址。Agent 从用户输入或上下文获取"),
|
|
52
|
+
tokens: z.string().describe("JSON 数组字符串。格式: [{\"chainIndex\":\"1\",\"tokenContractAddress\":\"0xabc...\"},{\"chainIndex\":\"56\",\"tokenContractAddress\":\"\"}]。chainIndex 用字符串, 主链币传空字符串。最多20个"),
|
|
53
|
+
excludeRiskToken: z.enum(["0","1"]).optional().describe("'0'=过滤风险/貔貅代币(默认), '1'=不过滤"),
|
|
54
|
+
},
|
|
55
|
+
{ readOnlyHint: true },
|
|
56
|
+
async ({ address, tokens, excludeRiskToken }) => {
|
|
57
|
+
if(!auth) return AUTH_REQUIRED("READ");
|
|
58
|
+
try {
|
|
59
|
+
let parsed: Array<{ chainIndex: string; tokenContractAddress: string }>;
|
|
60
|
+
try { parsed = JSON.parse(tokens); } catch { return toError(new Error("tokens 格式错误: 需要 JSON 数组, 例 [{\"chainIndex\":\"1\",\"tokenContractAddress\":\"0xabc...\"}]")); }
|
|
61
|
+
if (!Array.isArray(parsed) || parsed.length === 0 || parsed.length > 20) return toError(new Error("tokens 需为非空数组且最多20个元素"));
|
|
62
|
+
return toResult(await balanceApi.specificTokenBalance(auth, address, parsed, excludeRiskToken));
|
|
63
|
+
} catch(e) { return toError(e); }
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeFi 模块 — CAT:[链上-分析]
|
|
3
|
+
* 投资品查询: 链列表 + 协议 + 搜索 + 详情
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { defiApi } from "../adapters/onchainos.js";
|
|
8
|
+
import { toResult, toError, AUTH_REQUIRED } from "../adapters/shared.js";
|
|
9
|
+
import type { Auth } from "../adapters/shared.js";
|
|
10
|
+
|
|
11
|
+
export function registerDefiTools(server: McpServer, auth: Auth | null): void {
|
|
12
|
+
|
|
13
|
+
server.tool("onchainos_defi_supported_chains",
|
|
14
|
+
"CAT:[链上-分析] | ## 功能: 获取 DeFi 投资品覆盖的链, 返回 chainIndex(字符串)+network\n## 场景: 搜索/投资 DeFi 产品前确认目标链是否被覆盖\n## 关键词: DeFi, 链列表, chainIndex, network\n## 参数: 无\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 微小 ~2KB\n## 关联: 本工具 -> onchainos_defi_supported_platforms / onchainos_defi_search_products",
|
|
15
|
+
{}, { readOnlyHint: true },
|
|
16
|
+
async () => { if(!auth) return AUTH_REQUIRED("READ"); try { return toResult(await defiApi.supportedChains(auth)); } catch(e) { return toError(e); } },
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
server.tool("onchainos_defi_supported_platforms",
|
|
20
|
+
"CAT:[链上-分析] | ## 功能: 获取 DeFi 协议列表+统计, 返回 analysisPlatformId/platformName/investmentCount\n## 场景: 了解支持哪些协议, 每个协议有多少投资品\n## 关键词: DeFi, 协议, platform, Aave, Lido\n## 参数: 无\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 微小 ~5KB\n## 关联: onchainos_defi_supported_chains -> 本工具 -> onchainos_defi_search_products(按协议搜索)",
|
|
21
|
+
{}, { readOnlyHint: true },
|
|
22
|
+
async () => { if(!auth) return AUTH_REQUIRED("READ"); try { return toResult(await defiApi.supportedPlatforms(auth)); } catch(e) { return toError(e); } },
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
server.tool("onchainos_defi_search_products",
|
|
26
|
+
"CAT:[链上-分析] | ## 功能: 按代币/协议/链搜索 DeFi 投资品, 返回 investmentId/name/platformName/rate(APY)/tvl/feeRate\n## 场景: 用户想投 DeFi 时第一步 — 搜索可投产品\n## 关键词: DeFi, 搜索, 投资品, APY, TVL, product\n## 参数:\n## - tokenKeywordList: 代币关键词数组(必填, 如[\"USDC\",\"ETH\"])\n## - platformKeywordList: 协议关键词(可选)\n## - pageNum: 页码(可选, 默认1, pageSize=20)\n## - chainIndex: 链ID字符串(可选)\n## - productGroup: 投资类型(可选)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 中等 ~10KB\n## 关联: 本工具拿到 investmentId -> onchainos_defi_product_detail(详情) -> 交易执行",
|
|
27
|
+
{
|
|
28
|
+
tokenKeywordList: z.string().describe("代币关键词 JSON 数组字符串。如 '[\"USDC\",\"ETH\"]'"),
|
|
29
|
+
platformKeywordList: z.string().optional().describe("协议关键词 JSON 数组字符串。如 '[\"Aave V3\"]'"),
|
|
30
|
+
pageNum: z.number().int().min(1).optional().describe("页码, 最小1, 每页20条。默认1"),
|
|
31
|
+
chainIndex: z.string().optional().describe("链ID(字符串)。如 '1'=ETH '8453'=Base。不传查所有链"),
|
|
32
|
+
productGroup: z.enum(["SINGLE_EARN","DEX_POOL","LENDING"]).optional().describe("投资类型: SINGLE_EARN=单币赚币 DEX_POOL=流动性池 LENDING=借贷"),
|
|
33
|
+
},
|
|
34
|
+
{ readOnlyHint: true },
|
|
35
|
+
async ({ tokenKeywordList, platformKeywordList, pageNum, chainIndex, productGroup }) => {
|
|
36
|
+
if(!auth) return AUTH_REQUIRED("READ");
|
|
37
|
+
try {
|
|
38
|
+
let tokens: string[]; try { tokens = JSON.parse(tokenKeywordList); } catch { return toError(new Error("tokenKeywordList 格式错误: 需要 JSON 数组, 如 '[\"USDC\"]'")); }
|
|
39
|
+
let platforms: string[] | undefined;
|
|
40
|
+
if (platformKeywordList) { try { platforms = JSON.parse(platformKeywordList); } catch { return toError(new Error("platformKeywordList 格式错误: 需要 JSON 数组")); } }
|
|
41
|
+
return toResult(await defiApi.searchProducts(auth, { tokenKeywordList: tokens, platformKeywordList: platforms, pageNum, chainIndex, productGroup }));
|
|
42
|
+
} catch(e) { return toError(e); }
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
server.tool("onchainos_defi_product_detail",
|
|
47
|
+
"CAT:[链上-分析] | ## 功能: 获取投资品完整信息, 含 APY 明细/底层资产/isSupportClaim/isInvestable/isSupportRedeem\n## 场景: 用户选好产品后查看详情, 判断可执行操作(投资/赎回/领取奖励)\n## 关键词: DeFi, 投资品详情, APY, TVL, underlyingToken\n## 参数:\n## - investmentId: 投资品ID(必填)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 中等 ~5KB\n## 关联: onchainos_defi_search_products -> 本工具 -> 根据 isInvestable/isSupportRedeem 决定下一步",
|
|
48
|
+
{
|
|
49
|
+
investmentId: z.string().describe("投资品ID。从 onchainos_defi_search_products 返回值的 investmentId 字段获取"),
|
|
50
|
+
},
|
|
51
|
+
{ readOnlyHint: true },
|
|
52
|
+
async ({ investmentId }) => { if(!auth) return AUTH_REQUIRED("READ"); try { return toResult(await defiApi.productDetail(auth, investmentId)); } catch(e) { return toError(e); } },
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
server.tool("onchainos_defi_rate_chart",
|
|
56
|
+
"CAT:[链上-分析] | ## 功能: 获取投资品历史 APY 折线图数据, 返回 timestamp/rate/bonusRate/totalReward\n## 场景: 查看收益率走势, 判断最佳入场时机\n## 关键词: APY, 折线图, rate, 收益率, chart\n## 参数:\n## - investmentId: 投资品ID(必填)\n## - timeRange: 时间范围(可选, 默认WEEK)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 中等 ~10KB\n## 关联: onchainos_defi_product_detail -> 本工具",
|
|
57
|
+
{
|
|
58
|
+
investmentId: z.string().describe("投资品ID。从 onchainos_defi_search_products 返回值获取"),
|
|
59
|
+
timeRange: z.enum(["WEEK","MONTH","SEASON","YEAR"]).optional().describe("时间范围: WEEK=一周 MONTH=一月 SEASON=三月 YEAR=一年。默认WEEK"),
|
|
60
|
+
},
|
|
61
|
+
{ readOnlyHint: true },
|
|
62
|
+
async ({ investmentId, timeRange }) => { if(!auth) return AUTH_REQUIRED("READ"); try { return toResult(await defiApi.rateChart(auth, investmentId, timeRange)); } catch(e) { return toError(e); } },
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
server.tool("onchainos_defi_tvl_chart",
|
|
66
|
+
"CAT:[链上-分析] | ## 功能: 获取投资品历史 TVL 折线图数据, 返回 chartVos[{timestamp,tvl,limitValue}]\n## 场景: 评估投资品规模变化趋势, TVL 增长=市场信心增强\n## 关键词: TVL, 折线图, 锁仓量, chart\n## 参数:\n## - investmentId: 投资品ID(必填)\n## - timeRange: 时间范围(可选, 默认WEEK)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 中等 ~10KB\n## 关联: onchainos_defi_product_detail -> 本工具",
|
|
67
|
+
{
|
|
68
|
+
investmentId: z.string().describe("投资品ID"),
|
|
69
|
+
timeRange: z.enum(["WEEK","MONTH","SEASON","YEAR"]).optional().describe("时间范围。默认WEEK"),
|
|
70
|
+
},
|
|
71
|
+
{ readOnlyHint: true },
|
|
72
|
+
async ({ investmentId, timeRange }) => { if(!auth) return AUTH_REQUIRED("READ"); try { return toResult(await defiApi.tvlChart(auth, investmentId, timeRange)); } catch(e) { return toError(e); } },
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
server.tool("onchainos_defi_depth_price_chart",
|
|
76
|
+
"CAT:[链上-分析] | ## 功能: 获取 V3 Pool 流动性深度图或价格历史图。仅适用于 V3 DEX Pool\n## 场景: 分析 V3 池子流动性分布, 判断大额交易滑点\n## 关键词: V3, depth, 深度图, 价格图, liquidity\n## 参数:\n## - investmentId: 投资品ID(必填)\n## - chartType: 图表类型(可选, 默认DEPTH)\n## - timeRange: 时间范围(可选, chartType=PRICE时生效)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 中等 ~20KB\n## 关联: onchainos_defi_product_detail -> 本工具(仅V3 Pool)",
|
|
77
|
+
{
|
|
78
|
+
investmentId: z.string().describe("投资品ID"),
|
|
79
|
+
chartType: z.enum(["DEPTH","PRICE"]).optional().describe("图表类型: DEPTH=深度图(默认) PRICE=价格历史图"),
|
|
80
|
+
timeRange: z.enum(["DAY","WEEK"]).optional().describe("时间范围(仅chartType=PRICE时生效): DAY=24h WEEK=1周。默认DAY"),
|
|
81
|
+
},
|
|
82
|
+
{ readOnlyHint: true },
|
|
83
|
+
async ({ investmentId, chartType, timeRange }) => { if(!auth) return AUTH_REQUIRED("READ"); try { return toResult(await defiApi.depthPriceChart(auth, investmentId, chartType, timeRange)); } catch(e) { return toError(e); } },
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
server.tool("onchainos_defi_prepare_transaction",
|
|
87
|
+
"CAT:[链上-分析] | ## 功能: 申购/赎回前的参数准备, 返回可投币种(investWithTokenList)/凭证代币(receiveTokenInfo)/收益代币(gainsTokenList)\n## 场景: 调用 enter/exit 前必调, 确认可投什么币/收到什么凭证\n## 关键词: DeFi, prepare, 参数准备, investWithToken, receiveToken\n## 参数:\n## - investmentId: 投资品ID(必填)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 中等 ~5KB\n## 关联: onchainos_defi_product_detail -> 本工具 -> onchainos_defi_enter(申购) / onchainos_defi_exit(赎回)",
|
|
88
|
+
{ investmentId: z.string().describe("投资品ID。从 onchainos_defi_search_products 返回值获取") },
|
|
89
|
+
{ readOnlyHint: true },
|
|
90
|
+
async ({ investmentId }) => { if(!auth) return AUTH_REQUIRED("READ"); try { return toResult(await defiApi.prepareTransaction(auth, investmentId)); } catch(e) { return toError(e); } },
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
server.tool("onchainos_defi_calc_enter_info",
|
|
94
|
+
"CAT:[链上-分析] | ## 功能: V3 Pool 双币分配计算 - 输入单币金额+tick区间, 返回双币各需投入数量\n## 场景: V3 Pool 申购前计算 token0/token1 分配比例\n## 关键词: V3, 双币分配, calculator, tick, enter info\n## 参数:\n## - inputAmount/inputTokenAddress/tokenDecimal/investmentId/address/tickLower/tickUpper(全部必填)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 微小 ~2KB\n## 关联: onchainos_defi_prepare_transaction -> 本工具(仅V3 Pool) -> onchainos_defi_enter",
|
|
95
|
+
{
|
|
96
|
+
inputAmount: z.string().describe("单币金额, 人类可读(如'0.05')"),
|
|
97
|
+
inputTokenAddress: z.string().describe("输入的代币合约地址。可以是 V3 Pool 的 token0 或 token1"),
|
|
98
|
+
tokenDecimal: z.string().describe("输入代币精度(如'18','6')"),
|
|
99
|
+
investmentId: z.string().describe("投资品ID"),
|
|
100
|
+
address: z.string().describe("用户钱包地址"),
|
|
101
|
+
tickLower: z.string().describe("V3 tick 下限(如'-33500')"),
|
|
102
|
+
tickUpper: z.string().describe("V3 tick 上限(如'-30450')"),
|
|
103
|
+
},
|
|
104
|
+
{ readOnlyHint: true },
|
|
105
|
+
async (params) => { if(!auth) return AUTH_REQUIRED("READ"); try { return toResult(await defiApi.calcEnterInfo(auth, params)); } catch(e) { return toError(e); } },
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
server.tool("onchainos_defi_enter",
|
|
109
|
+
"CAT:[链上-分析] | ## 功能: DeFi 申购/存款/借款, 返回顺序执行的 calldata 列表(APPROVE->DEPOSIT)\n## 场景: 准备好参数后执行投资操作\n## 关键词: DeFi, 申购, enter, deposit, borrow, calldata\n## 参数:\n## - investmentId/address(必填)\n## - userInputList: 投入代币 JSON 数组(必填)\n## - tickLower/tickUpper: V3 Pool 参数(可选)\n## - tokenId: V3 NFT position ID(可选)\n## - slippage: 滑点(可选, 默认0.01=1%)\n## 鉴权: 需要 API Key(交易)\n## 风险: WRITE - 返回 calldata, 需签名后广播\n## 返回量: 中等 ~5KB\n## 关联: onchainos_defi_prepare_transaction -> 本工具 -> onchainos_gateway_broadcast",
|
|
110
|
+
{
|
|
111
|
+
investmentId: z.string().describe("投资品ID"),
|
|
112
|
+
address: z.string().describe("用户钱包地址"),
|
|
113
|
+
userInputList: z.string().describe("投入代币 JSON 数组。如 '[{\"tokenAddress\":\"0x...\",\"chainIndex\":\"1\",\"coinAmount\":\"0.05\"}]'。从 onchainos_defi_prepare_transaction 的 investWithTokenList 获取"),
|
|
114
|
+
tickLower: z.string().optional().describe("V3 tick 下限。仅 Dex Pool 新建仓位时需要"),
|
|
115
|
+
tickUpper: z.string().optional().describe("V3 tick 上限。仅 Dex Pool 新建仓位时需要"),
|
|
116
|
+
tokenId: z.string().optional().describe("V3 NFT position token ID。向已有仓位追加时必传"),
|
|
117
|
+
slippage: z.string().optional().describe("滑点。'0.01'=1% '0.1'=10%。默认'0.01'"),
|
|
118
|
+
},
|
|
119
|
+
{ destructiveHint: true },
|
|
120
|
+
async ({ investmentId, address, userInputList, tickLower, tickUpper, tokenId, slippage }) => {
|
|
121
|
+
if(!auth) return AUTH_REQUIRED("TRADE");
|
|
122
|
+
try {
|
|
123
|
+
let input: unknown; try { input = JSON.parse(userInputList); } catch { return toError(new Error("userInputList 格式错误: 需要 JSON 数组")); }
|
|
124
|
+
const data = await defiApi.enter(auth, {
|
|
125
|
+
investmentId, address, userInputList: input,
|
|
126
|
+
...(tickLower ? { tickLower } : {}), ...(tickUpper ? { tickUpper } : {}),
|
|
127
|
+
...(tokenId ? { tokenId } : {}), slippage,
|
|
128
|
+
});
|
|
129
|
+
return toResult(data, {
|
|
130
|
+
nextSteps: [
|
|
131
|
+
{ action: "按 dataList 顺序执行: APPROVE->签名->广播, 然后 DEPOSIT->签名->广播", tool: "onchainos_gateway_broadcast", condition: "dataList 中每个元素是一个交易步骤" },
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
} catch(e) { return toError(e); }
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
server.tool("onchainos_defi_exit",
|
|
139
|
+
"CAT:[链上-分析] | ## 功能: DeFi 赎回/还款, 返回顺序执行的 calldata 列表\n## 场景: 退出投资或偿还借款\n## 关键词: DeFi, 赎回, exit, withdraw, repay, calldata\n## 参数:\n## - investmentId/address(必填)\n## - redeemPercent: 赎回比例(建议传, \"1\"=100%)\n## - userInputList: 输入代币 JSON(可选)\n## - tokenId: V3 NFT position ID(V3赎回必传)\n## - slippage: 滑点(可选)\n## 鉴权: 需要 API Key(交易)\n## 风险: WRITE - 返回 calldata\n## 返回量: 中等 ~5KB\n## 关联: onchainos_defi_prepare_transaction -> 本工具 -> onchainos_gateway_broadcast",
|
|
140
|
+
{
|
|
141
|
+
investmentId: z.string().describe("投资品ID"),
|
|
142
|
+
address: z.string().describe("用户钱包地址"),
|
|
143
|
+
redeemPercent: z.string().optional().describe("赎回比例, 建议必传。'1'=100% '0.5'=50%。不传可能导致 aToken 动态余额 revert"),
|
|
144
|
+
userInputList: z.string().optional().describe("输入代币 JSON 数组。Farm/v2pool 传 LP Token, 其他情况传目标接收 token"),
|
|
145
|
+
tokenId: z.string().optional().describe("V3 NFT position token ID。V3 Pool 赎回必传"),
|
|
146
|
+
slippage: z.string().optional().describe("滑点。默认'0.01'"),
|
|
147
|
+
},
|
|
148
|
+
{ destructiveHint: true },
|
|
149
|
+
async ({ investmentId, address, redeemPercent, userInputList, tokenId, slippage }) => {
|
|
150
|
+
if(!auth) return AUTH_REQUIRED("TRADE");
|
|
151
|
+
try {
|
|
152
|
+
const body: Record<string, unknown> = { investmentId, address };
|
|
153
|
+
if (redeemPercent) body.redeemPercent = redeemPercent;
|
|
154
|
+
if (userInputList) { try { body.userInputList = JSON.parse(userInputList); } catch { return toError(new Error("userInputList 格式错误")); } }
|
|
155
|
+
if (tokenId) body.tokenId = tokenId;
|
|
156
|
+
if (slippage) body.slippage = slippage;
|
|
157
|
+
return toResult(await defiApi.exit(auth, body), {
|
|
158
|
+
nextSteps: [{ action: "按 dataList 顺序签名并广播", tool: "onchainos_gateway_broadcast" }],
|
|
159
|
+
});
|
|
160
|
+
} catch(e) { return toError(e); }
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
server.tool("onchainos_defi_claim",
|
|
165
|
+
"CAT:[链上-分析] | ## 功能: 领取 DeFi 协议奖励(矿币/手续费/Bonus/到期本金)\n## 场景: 定期领取 DeFi 投资收益\n## 关键词: DeFi, claim, 奖励, reward, bonus, 领取\n## 参数:\n## - address(必填)\n## - rewardType: 奖励类型(必填, 见描述)\n## - investmentId: 投资品ID(大部分类型必传)\n## - analysisPlatformId/tokenId/principalIndex: 按 rewardType 选填\n## - expectOutputList: 预期输出 JSON(REWARD_MERKLE_BONUS 用)\n## 鉴权: 需要 API Key(交易)\n## 风险: WRITE - 返回 calldata\n## 返回量: 微小 ~3KB\n## 关联: onchainos_defi_user_platform_list -> 本工具",
|
|
166
|
+
{
|
|
167
|
+
address: z.string().describe("用户钱包地址"),
|
|
168
|
+
rewardType: z.enum(["REWARD_INVESTMENT","REWARD_PLATFORM","V3_FEE","REWARD_OKX_BONUS","REWARD_MERKLE_BONUS","UNLOCKED_PRINCIPAL"]).describe("奖励类型: REWARD_INVESTMENT=矿币奖励 REWARD_PLATFORM=协议奖励 V3_FEE=V3手续费 REWARD_OKX_BONUS=OKX Bonus REWARD_MERKLE_BONUS=Merkle Bonus UNLOCKED_PRINCIPAL=到期本金"),
|
|
169
|
+
investmentId: z.string().optional().describe("投资品ID。除 REWARD_PLATFORM 外都需传"),
|
|
170
|
+
analysisPlatformId: z.string().optional().describe("协议ID。REWARD_PLATFORM 时填写"),
|
|
171
|
+
tokenId: z.string().optional().describe("V3 仓位 tokenId。V3_FEE 时填写"),
|
|
172
|
+
principalIndex: z.string().optional().describe("到期订单 index。UNLOCKED_PRINCIPAL 时填写"),
|
|
173
|
+
expectOutputList: z.string().optional().describe("预期输出 JSON 数组。REWARD_MERKLE_BONUS 时填写, 如 '[{\"chainIndex\":\"1\",\"tokenAddress\":\"0x...\",\"coinAmount\":\"0.001\"}]'"),
|
|
174
|
+
},
|
|
175
|
+
{ destructiveHint: true },
|
|
176
|
+
async ({ address, rewardType, investmentId, analysisPlatformId, tokenId, principalIndex, expectOutputList }) => {
|
|
177
|
+
if(!auth) return AUTH_REQUIRED("TRADE");
|
|
178
|
+
try {
|
|
179
|
+
const body: Record<string, unknown> = { address, rewardType };
|
|
180
|
+
if (investmentId) body.investmentId = investmentId;
|
|
181
|
+
if (analysisPlatformId) body.analysisPlatformId = analysisPlatformId;
|
|
182
|
+
if (tokenId) body.tokenId = tokenId;
|
|
183
|
+
if (principalIndex) body.principalIndex = principalIndex;
|
|
184
|
+
if (expectOutputList) { try { body.expectOutputList = JSON.parse(expectOutputList); } catch { return toError(new Error("expectOutputList 格式错误")); } }
|
|
185
|
+
return toResult(await defiApi.claim(auth, body));
|
|
186
|
+
} catch(e) { return toError(e); }
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
server.tool("onchainos_defi_user_platform_list",
|
|
191
|
+
"CAT:[链上-分析] | ## 功能: 查询用户在各 DeFi 协议的持仓概览, 返回 platformName/analysisPlatformId/currencyAmount(USD)/investmentCount\n## 场景: 用户想看 DeFi 资产分布时第一步 - 按协议汇总\n## 关键词: DeFi, 持仓, 协议, platform, asset, portfolio\n## 参数:\n## - walletAddressList: 钱包地址 JSON 数组(必填)\n## - tag: 自定义标签(可选)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 中等 ~20KB\n## 关联: 本工具拿 analysisPlatformId -> onchainos_defi_user_platform_detail(明细) -> onchainos_defi_exit/claim",
|
|
192
|
+
{
|
|
193
|
+
walletAddressList: z.string().describe("钱包地址 JSON 数组。如 '[{\"chainIndex\":\"1\",\"walletAddress\":\"0x...\"},{\"chainIndex\":\"56\",\"walletAddress\":\"0x...\"}]'。支持多链多钱包"),
|
|
194
|
+
tag: z.string().optional().describe("自定义标签, 用于标记查询"),
|
|
195
|
+
},
|
|
196
|
+
{ readOnlyHint: true },
|
|
197
|
+
async ({ walletAddressList, tag }) => {
|
|
198
|
+
if(!auth) return AUTH_REQUIRED("READ");
|
|
199
|
+
try {
|
|
200
|
+
let list: Array<{ chainIndex: string; walletAddress: string; pubKey?: string }>;
|
|
201
|
+
try { list = JSON.parse(walletAddressList); } catch { return toError(new Error("walletAddressList 格式错误: 需要 JSON 数组")); }
|
|
202
|
+
return toResult(await defiApi.userPlatformList(auth, { walletAddressList: list, ...(tag ? { tag } : {}) }));
|
|
203
|
+
} catch(e) { return toError(e); }
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
server.tool("onchainos_defi_user_platform_detail",
|
|
208
|
+
"CAT:[链上-分析] | ## 功能: 查询用户在某协议的详细持仓, 返回每个投资品的仓位/资产/奖励详情\n## 场景: 看某个协议的具体投资分布、可领取奖励、仓位状态\n## 关键词: DeFi, 持仓明细, position, reward, availableRewards\n## 参数:\n## - walletAddressList: 钱包地址 JSON 数组(必填)\n## - platformList: 协议 JSON 数组(必填, 含 chainIndex+analysisPlatformId)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 大 ~100KB\n## 关联: onchainos_defi_user_platform_list -> 本工具 -> onchainos_defi_claim(领奖励)/onchainos_defi_exit(赎回)",
|
|
209
|
+
{
|
|
210
|
+
walletAddressList: z.string().describe("钱包地址 JSON 数组。如 '[{\"chainIndex\":\"1\",\"walletAddress\":\"0x...\"}]'"),
|
|
211
|
+
platformList: z.string().describe("协议 JSON 数组。如 '[{\"chainIndex\":\"1\",\"analysisPlatformId\":\"44\"}]'。analysisPlatformId 从 onchainos_defi_user_platform_list 获取"),
|
|
212
|
+
},
|
|
213
|
+
{ readOnlyHint: true },
|
|
214
|
+
async ({ walletAddressList, platformList }) => {
|
|
215
|
+
if(!auth) return AUTH_REQUIRED("READ");
|
|
216
|
+
try {
|
|
217
|
+
let wallets: unknown; try { wallets = JSON.parse(walletAddressList); } catch { return toError(new Error("walletAddressList 格式错误")); }
|
|
218
|
+
let platforms: unknown; try { platforms = JSON.parse(platformList); } catch { return toError(new Error("platformList 格式错误")); }
|
|
219
|
+
return toResult(await defiApi.userPlatformDetail(auth, { walletAddressList: wallets, platformList: platforms }));
|
|
220
|
+
} catch(e) { return toError(e); }
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway 模块 — CAT:[链上-网关]
|
|
3
|
+
* 交易上链: Gas预估 + 模拟 + 广播
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { gatewayApi, postTxApi } from "../adapters/onchainos.js";
|
|
8
|
+
import { toResult, toError, AUTH_REQUIRED } from "../adapters/shared.js";
|
|
9
|
+
import type { Auth } from "../adapters/shared.js";
|
|
10
|
+
|
|
11
|
+
export function registerGatewayTools(server: McpServer, auth: Auth | null): void {
|
|
12
|
+
|
|
13
|
+
server.tool("onchainos_gateway_supported_chain",
|
|
14
|
+
"CAT:[链上-网关] | ## 功能: 获取交易上链 API 支持的链, 返回 chainIndex(字符串)/name/shortName\n## 场景: Agent 广播/查gas前确认目标链是否被 Gateway 支持\n## 关键词: gateway, 链列表, chainIndex, broadcast\n## 参数: 无\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 微小 ~2KB\n## 关联: 本工具 -> onchainos_gateway_gas_price / onchainos_gateway_broadcast",
|
|
15
|
+
{}, { readOnlyHint: true },
|
|
16
|
+
async () => { if(!auth) return AUTH_REQUIRED("READ"); try { return toResult(await gatewayApi.supportedChain(auth)); } catch(e) { return toError(e); } },
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
server.tool("onchainos_gateway_gas_price",
|
|
20
|
+
"CAT:[链上-网关] | ## 功能: 获取指定链的 Gas Price 推荐值(低/中/高三档)\n## 场景: 交易前估算 gas 成本。EVM链返回 normal/min/max(wei) + EIP1559字段, Solana返回 priorityFee 各档位(microlamports)\n## 关键词: gas price, 矿工费, 成本, EIP1559, priority fee\n## 参数:\n## - chainIndex: 链索引字符串(必填)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 微小 ~1KB\n## 关联: onchainos_gateway_supported_chain -> 本工具 -> onchainos_gateway_gas_limit(估limit) / onchainos_gateway_simulate(模拟)",
|
|
21
|
+
{
|
|
22
|
+
chainIndex: z.string().describe("链索引(字符串)。'1'=ETH '56'=BSC '501'=Solana。从 onchainos_gateway_supported_chain 获取"),
|
|
23
|
+
},
|
|
24
|
+
{ readOnlyHint: true },
|
|
25
|
+
async ({ chainIndex }) => { if(!auth) return AUTH_REQUIRED("READ"); try { return toResult(await gatewayApi.gasPrice(auth, chainIndex)); } catch(e) { return toError(e); } },
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
server.tool("onchainos_gateway_gas_limit",
|
|
29
|
+
"CAT:[链上-网关] | ## 功能: 预执行交易获取预估 Gas Limit\n## 场景: 知道 gas 用量后计算总费用 = gasPrice * gasLimit\n## 关键词: gas limit, 用量预估, 费用\n## 参数:\n## - chainIndex: 链索引(必填)\n## - fromAddress: 发送方地址(必填)\n## - toAddress: 接收方/合约地址(必填)\n## - txAmount: 主链币数量(可选, wei单位)\n## - extJson.inputData: calldata(可选)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 微小 ~1KB\n## 关联: onchainos_gateway_gas_price -> 本工具 -> onchainos_gateway_simulate",
|
|
30
|
+
{
|
|
31
|
+
chainIndex: z.string().describe("链索引(字符串)。从 onchainos_gateway_supported_chain 获取"),
|
|
32
|
+
fromAddress: z.string().describe("发送方地址(钱包地址)"),
|
|
33
|
+
toAddress: z.string().describe("接收方地址。转账=代币地址/钱包地址, 兑换=OKX DEX router地址, 授权=代币地址"),
|
|
34
|
+
txAmount: z.string().optional().describe("主链币金额(wei单位)。主链币交易时填数量, 代币交易填'0'。可从 swap 返回值 tx.value 获取"),
|
|
35
|
+
inputData: z.string().optional().describe("calldata(hex)。从 swap/approve 返回值获取"),
|
|
36
|
+
},
|
|
37
|
+
{ readOnlyHint: true },
|
|
38
|
+
async ({ chainIndex, fromAddress, toAddress, txAmount, inputData }) => {
|
|
39
|
+
if(!auth) return AUTH_REQUIRED("READ");
|
|
40
|
+
try {
|
|
41
|
+
const extJson = inputData ? { inputData } : undefined;
|
|
42
|
+
return toResult(await gatewayApi.gasLimit(auth, { chainIndex, fromAddress, toAddress, txAmount, extJson }));
|
|
43
|
+
} catch(e) { return toError(e); }
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
server.tool("onchainos_gateway_simulate",
|
|
48
|
+
"CAT:[链上-网关] | ## 功能: 模拟执行交易, 不消耗 gas, 返回资产变化/failReason/risks\n## 场景: 广播前必调 - 验证交易是否 revert、资产变化是否符合预期。需白名单, 联系 dexapi@okx.com\n## 关键词: 模拟, simulate, 预执行, 资产变化, failReason, 风险\n## 参数:\n## - fromAddress: 发送方(必填)\n## - toAddress: 接收方/合约(必填)\n## - chainIndex: 链索引字符串(必填)\n## - inputData: calldata hex(必填)\n## - txAmount: 主链币金额 wei(可选, 默认0)\n## - gasPrice: gas price(可选)\n## - priorityFee: Solana优先费(可选)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 仅模拟, 不上链\n## 返回量: 微小 ~3KB\n## 关联: onchainos_gateway_gas_limit -> 本工具模拟 -> 模拟通过后 onchainos_gateway_broadcast",
|
|
49
|
+
{
|
|
50
|
+
fromAddress: z.string().describe("发送方地址(钱包地址)"),
|
|
51
|
+
toAddress: z.string().describe("接收方地址。兑换=OKX DEX router, 授权=代币地址"),
|
|
52
|
+
chainIndex: z.string().describe("链索引(字符串)。如 '1'=ETH '501'=Solana"),
|
|
53
|
+
inputData: z.string().describe("calldata(hex, base58编码)。从 swap/approve 返回值获取"),
|
|
54
|
+
txAmount: z.string().optional().describe("主链币金额(wei)。主链币交易填数量, 代币交易填'0'"),
|
|
55
|
+
gasPrice: z.string().optional().describe("Gas price(wei), 不填用当前网络价"),
|
|
56
|
+
priorityFee: z.string().optional().describe("优先费。仅 Solana, 单位 microlamports"),
|
|
57
|
+
},
|
|
58
|
+
{ readOnlyHint: true },
|
|
59
|
+
async ({ fromAddress, toAddress, chainIndex, inputData, txAmount, gasPrice, priorityFee }) => {
|
|
60
|
+
if(!auth) return AUTH_REQUIRED("READ");
|
|
61
|
+
try {
|
|
62
|
+
return toResult(await gatewayApi.simulate(auth, {
|
|
63
|
+
fromAddress, toAddress, chainIndex,
|
|
64
|
+
txAmount,
|
|
65
|
+
extJson: { inputData },
|
|
66
|
+
gasPrice, priorityFee,
|
|
67
|
+
}));
|
|
68
|
+
} catch(e) { return toError(e); }
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
server.tool("onchainos_gateway_broadcast",
|
|
73
|
+
"CAT:[链上-网关] | ## 功能: 广播已签名交易到链上, 返回 orderId/txHash\n## 场景: Agent 拿到用户签名后提交上链。支持 MEV 保护(Solana可传 jitoSignedTx)\n## 关键词: 广播, broadcast, 上链, MEV保护, txHash\n## 参数:\n## - signedTx: 签名后交易hex(必填)\n## - chainIndex: 链索引字符串(必填)\n## - address: 用户地址(必填)\n## - enableMevProtection: 启用防夹(可选)\n## - jitoSignedTx: Solana Jito签名交易(可选, SOL专用)\n## 鉴权: 需要 API Key(交易)\n## 风险: WRITE - 实际执行上链\n## 返回量: 微小 ~1KB\n## 关联: onchainos_gateway_simulate 模拟通过 -> 用户签名 -> 本工具广播 -> onchainos_gateway_orders 查订单状态",
|
|
74
|
+
{
|
|
75
|
+
signedTx: z.string().describe("用户签名后的完整交易 hex。用户用私钥/钱包对 calldata 签名后得到"),
|
|
76
|
+
chainIndex: z.string().describe("链索引(字符串)。如 '1'=ETH '501'=Solana"),
|
|
77
|
+
address: z.string().describe("用户钱包地址"),
|
|
78
|
+
enableMevProtection: z.boolean().optional().describe("启用 MEV 防夹保护。仅 ETH/BSC/SOL/BASE 支持"),
|
|
79
|
+
jitoSignedTx: z.string().optional().describe("Solana Jito 签名交易(base58)。仅 SOL, signedTx 和 jitoSignedTx 必须同时填"),
|
|
80
|
+
},
|
|
81
|
+
{ destructiveHint: true },
|
|
82
|
+
async ({ signedTx, chainIndex, address, enableMevProtection, jitoSignedTx }) => {
|
|
83
|
+
if(!auth) return AUTH_REQUIRED("TRADE");
|
|
84
|
+
try {
|
|
85
|
+
const extraData = (enableMevProtection !== undefined || jitoSignedTx)
|
|
86
|
+
? JSON.stringify({ enableMevProtection: enableMevProtection ?? false, ...(jitoSignedTx ? { jitoSignedTx } : {}) })
|
|
87
|
+
: undefined;
|
|
88
|
+
const data = await gatewayApi.broadcast(auth, { signedTx, chainIndex, address, extraData });
|
|
89
|
+
return toResult(data, {
|
|
90
|
+
nextSteps: [
|
|
91
|
+
{ action: "查订单状态", tool: "onchainos_gateway_orders", params: { address, chainIndex, orderId: "{{返回的 orderId}}" } },
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
} catch(e) { return toError(e); }
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
server.tool("onchainos_gateway_orders",
|
|
99
|
+
"CAT:[链上-网关] | ## 功能: 查询广播订单列表, 按时间倒序, 返回 txStatus(1排队/2成功/3失败)/txHash\n## 场景: 广播后确认交易是否成功上链\n## 关键词: 订单, orders, txStatus, 确认, 广播记录\n## 参数:\n## - address: 地址(必填)\n## - chainIndex: 链索引字符串(必填)\n## - txStatus: 状态筛选(可选)\n## - orderId: 订单ID(可选)\n## - cursor: 分页游标(可选)\n## - limit: 返回条数(可选, 默认20, 最多100)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 中等 ~10KB\n## 关联: onchainos_gateway_broadcast -> 本工具 -> onchainos_transaction_detail 看详情",
|
|
100
|
+
{
|
|
101
|
+
address: z.string().describe("钱包地址"),
|
|
102
|
+
chainIndex: z.string().describe("链索引(字符串)。如 '1'=ETH"),
|
|
103
|
+
txStatus: z.enum(["1","2","3"]).optional().describe("交易状态: '1'=排队中 '2'=成功 '3'=失败"),
|
|
104
|
+
orderId: z.string().optional().describe("订单ID。从 onchainos_gateway_broadcast 返回值获取"),
|
|
105
|
+
cursor: z.string().optional().describe("分页游标。首次不传, 后续从返回值取"),
|
|
106
|
+
limit: z.string().optional().describe("返回条数, 默认20, 最多100"),
|
|
107
|
+
},
|
|
108
|
+
{ readOnlyHint: true },
|
|
109
|
+
async ({ address, chainIndex, txStatus, orderId, cursor, limit }) => {
|
|
110
|
+
if(!auth) return AUTH_REQUIRED("READ");
|
|
111
|
+
try { return toResult(await postTxApi.orders(auth, address, chainIndex, txStatus, orderId, cursor, limit)); } catch(e) { return toError(e); }
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
}
|