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.
Files changed (56) hide show
  1. package/.env.example +5 -0
  2. package/AGENT-MCP-RULES.md +248 -0
  3. package/OnchainOS-API/345/257/271/346/216/245/350/247/204/350/214/203.md +329 -0
  4. package/README.md +106 -0
  5. package/dist/adapters/onchainos-ws.d.ts +31 -0
  6. package/dist/adapters/onchainos-ws.js +103 -0
  7. package/dist/adapters/onchainos.d.ts +343 -0
  8. package/dist/adapters/onchainos.js +275 -0
  9. package/dist/adapters/shared.d.ts +23 -0
  10. package/dist/adapters/shared.js +107 -0
  11. package/dist/http.d.ts +9 -0
  12. package/dist/http.js +124 -0
  13. package/dist/index.d.ts +6 -0
  14. package/dist/index.js +54 -0
  15. package/dist/tools/balance.d.ts +4 -0
  16. package/dist/tools/balance.js +69 -0
  17. package/dist/tools/defi.d.ts +4 -0
  18. package/dist/tools/defi.js +268 -0
  19. package/dist/tools/gateway.d.ts +4 -0
  20. package/dist/tools/gateway.js +107 -0
  21. package/dist/tools/intent.d.ts +4 -0
  22. package/dist/tools/intent.js +111 -0
  23. package/dist/tools/market.d.ts +4 -0
  24. package/dist/tools/market.js +612 -0
  25. package/dist/tools/payments.d.ts +4 -0
  26. package/dist/tools/payments.js +100 -0
  27. package/dist/tools/skills.d.ts +4 -0
  28. package/dist/tools/skills.js +464 -0
  29. package/dist/tools/trade.d.ts +4 -0
  30. package/dist/tools/trade.js +195 -0
  31. package/dist/tools/txhistory.d.ts +4 -0
  32. package/dist/tools/txhistory.js +49 -0
  33. package/dist/tools/ws.d.ts +4 -0
  34. package/dist/tools/ws.js +64 -0
  35. package/docs/DEFI.md +65 -0
  36. package/docs/GATEWAY.md +45 -0
  37. package/docs/MARKET.md +109 -0
  38. package/docs/PAYMENTS.md +83 -0
  39. package/docs/TRADE.md +103 -0
  40. package/package.json +25 -0
  41. package/src/adapters/onchainos-ws.ts +126 -0
  42. package/src/adapters/onchainos.ts +488 -0
  43. package/src/adapters/shared.ts +100 -0
  44. package/src/http.ts +132 -0
  45. package/src/index.ts +57 -0
  46. package/src/tools/balance.ts +67 -0
  47. package/src/tools/defi.ts +224 -0
  48. package/src/tools/gateway.ts +115 -0
  49. package/src/tools/intent.ts +106 -0
  50. package/src/tools/market.ts +543 -0
  51. package/src/tools/payments.ts +105 -0
  52. package/src/tools/skills.ts +489 -0
  53. package/src/tools/trade.ts +197 -0
  54. package/src/tools/txhistory.ts +51 -0
  55. package/src/tools/ws.ts +72 -0
  56. package/tsconfig.json +18 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Payments 模块 — CAT:[链上-支付]
3
+ * Agent Payments Protocol: Agent端 A2A (单次支付) + HTTP端 x402
4
+ *
5
+ * 路径来源: https://web3.okx.com API 参考
6
+ * Agent端: /api/v6/pay/a2a/payment/create | /p/{id} | /p/{id}/credential | /p/{id}/status
7
+ * HTTP端: /api/v6/pay/x402/supported | verify | settle | settle/status
8
+ */
9
+ import { z } from "zod";
10
+ import { paymentsApi } from "../adapters/onchainos.js";
11
+ import { toResult, toError, AUTH_REQUIRED } from "../adapters/shared.js";
12
+ export function registerPaymentsTools(server, auth) {
13
+ // ── Agent 端 A2A 单次支付 ─────────────────────────────
14
+ server.tool("onchainos_payment_create", "CAT:[链上-支付] | ## 功能: Seller Agent 创建单次付款(charge), 返回 paymentId+challenge+付款链接\n## 场景: Agent 卖家在对话中需要收费时调此工具, 拿到付款链接发给买家\n## 关键词: payment, create, charge, seller, 收款, a2a\n## 参数:\n## - amount: 收款金额, 人类可读(必填, 如'0.1'=0.1 USDT0)\n## - symbol: 代币符号(必填, USD_T0/USDC/USDG)\n## - recipient: 收款钱包地址(必填)\n## - description: 付款描述(可选)\n## - externalId: 业务订单号(可选, 幂等用)\n## - expiresIn: 有效期秒数(可选, 默认1800=30分钟)\n## 鉴权: 需要 API Key(交易)\n## 风险: WRITE - 创建付款订单\n## 返回量: 微小 ~2KB\n## 关联: 本工具创建 -> 把 paymentId/付款链接发给买家 -> 买家调 onchainos_payment_detail -> onchainos_payment_submit -> onchainos_payment_status", {
15
+ amount: z.string().describe("收款金额, 人类可读(如'0.1'=0.1 USDT0)"),
16
+ symbol: z.enum(["USD₮0", "USDC", "USDG"]).describe("代币符号: USD_T0/USDC/USDG"),
17
+ recipient: z.string().describe("Seller 收款钱包地址"),
18
+ description: z.string().optional().describe("付款描述, 如'翻译服务-3000字'"),
19
+ externalId: z.string().optional().describe("Seller 业务订单号, 用于幂等"),
20
+ expiresIn: z.number().int().optional().describe("有效期(秒), 默认1800=30分钟"),
21
+ }, { destructiveHint: true }, async ({ amount, symbol, recipient, description, externalId, expiresIn }) => {
22
+ if (!auth)
23
+ return AUTH_REQUIRED("TRADE");
24
+ try {
25
+ const data = await paymentsApi.create(auth, {
26
+ type: "charge", amount, symbol, recipient,
27
+ ...(description ? { description } : {}),
28
+ ...(externalId ? { externalId } : {}),
29
+ ...(expiresIn ? { expiresIn } : {}),
30
+ deliveries: { includeUrl: true },
31
+ });
32
+ return toResult(data, {
33
+ nextSteps: [
34
+ { action: "把付款链接发给买家", tool: "onchainos_payment_detail", condition: "通过消息通道或对话发送 https://pay.okx.com/p/{paymentId}" },
35
+ { action: "买家拉取详情", tool: "onchainos_payment_detail", params: { paymentId: "{{data.paymentId}}" }, condition: "买家侧操作" },
36
+ { action: "买家签名后提交", tool: "onchainos_payment_submit", params: { paymentId: "{{data.paymentId}}" }, condition: "买家侧操作" },
37
+ { action: "轮询结算结果", tool: "onchainos_payment_status", params: { paymentId: "{{data.paymentId}}" }, condition: "提交后轮询" },
38
+ ],
39
+ });
40
+ }
41
+ catch (e) {
42
+ return toError(e);
43
+ }
44
+ });
45
+ server.tool("onchainos_payment_detail", "CAT:[链上-支付] | ## 功能: Buyer 拉取付款详情(challenge), 含金额/收款方/代币/过期时间\n## 场景: 买家拿到 paymentId 或付款链接后查看付款信息\n## 关键词: payment, detail, challenge, buyer, a2a\n## 参数:\n## - paymentId: 付款ID(必填, 从付款链接或卖家消息获取)\n## 鉴权: PUBLIC - 公开接口, 无需 API Key\n## 风险: READ - 只读查询\n## 返回量: 微小 ~2KB\n## 关联: 卖家发 paymentId -> 本工具 -> onchainos_payment_submit", {
46
+ paymentId: z.string().describe("付款ID。格式 a2a_XXXX。从付款链接 https://pay.okx.com/p/{paymentId} 或卖家消息获取"),
47
+ }, { readOnlyHint: true }, async ({ paymentId }) => {
48
+ try {
49
+ return toResult(await paymentsApi.detail(paymentId));
50
+ }
51
+ catch (e) {
52
+ return toError(e);
53
+ }
54
+ });
55
+ server.tool("onchainos_payment_submit", "CAT:[链上-支付] | ## 功能: Buyer 提交 EIP-3009 签名凭证, Broker 验签后上链结算\n## 场景: 买家签名后提交支付, 资金直接从买家转给卖家\n## 关键词: payment, submit, credential, EIP-3009, 签名, a2a\n## 参数:\n## - paymentId: 付款ID(必填)\n## - authorization: EIP-3009 授权 JSON 字符串(必填)\n## {type:\"eip-3009\",from,to,value,validAfter,validBefore,nonce}\n## - signature: EIP-712 签名 hex(必填, 65字节)\n## 鉴权: PUBLIC - 公开接口, 无需 API Key\n## 风险: WRITE - 触发链上结算\n## 返回量: 微小 ~1KB\n## 关联: onchainos_payment_detail -> 买家签名 -> 本工具 -> onchainos_payment_status", {
56
+ paymentId: z.string().describe("付款ID"),
57
+ authorization: z.string().describe("EIP-3009 授权 JSON。格式: {\"type\":\"eip-3009\",\"from\":\"0x买家地址\",\"to\":\"0x卖家地址\",\"value\":\"100000\",\"validAfter\":\"0\",\"validBefore\":\"1714521600\",\"nonce\":\"0x...\"}。value 必须与 challenge 中 amount 一致"),
58
+ signature: z.string().describe("EIP-712 签名(0x前缀, 65字节 hex)。用买家钱包对 authorization 签名得到"),
59
+ }, { destructiveHint: true }, async ({ paymentId, authorization, signature }) => {
60
+ try {
61
+ let auth;
62
+ try {
63
+ auth = JSON.parse(authorization);
64
+ }
65
+ catch {
66
+ return toError(new Error("authorization 格式错误"));
67
+ }
68
+ return toResult(await paymentsApi.submit(paymentId, {
69
+ payload: { type: "transaction", signature, authorization: auth },
70
+ }), {
71
+ nextSteps: [
72
+ { action: "查询结算状态", tool: "onchainos_payment_status", params: { paymentId } },
73
+ { action: "交易确认后 Seller 交付资源", tool: "onchainos_payment_status", condition: "status=completed 时" },
74
+ ],
75
+ });
76
+ }
77
+ catch (e) {
78
+ return toError(e);
79
+ }
80
+ });
81
+ server.tool("onchainos_payment_status", "CAT:[链上-支付] | ## 功能: 查询支付状态(pending/settling/completed/failed/expired)\n## 场景: 提交凭证后轮询确认结算结果, 拿到 txHash\n## 关键词: payment, status, 状态, txHash, a2a\n## 参数:\n## - paymentId: 付款ID(必填)\n## 鉴权: PUBLIC - 公开接口, 无需 API Key\n## 风险: READ - 只读查询\n## 返回量: 微小 ~1KB\n## 关联: onchainos_payment_submit -> 本工具", {
82
+ paymentId: z.string().describe("付款ID"),
83
+ }, { readOnlyHint: true }, async ({ paymentId }) => {
84
+ try {
85
+ return toResult(await paymentsApi.status(paymentId));
86
+ }
87
+ catch (e) {
88
+ return toError(e);
89
+ }
90
+ });
91
+ // ── HTTP 端基础设施 ───────────────────────────────────
92
+ server.tool("onchainos_payment_supported", "CAT:[链上-支付] | ## 功能: 获取支付支持的网络/代币/scheme 列表\n## 场景: 发起支付前确认目标链和代币是否支持\n## 关键词: x402, payment, 支持列表, network, token\n## 参数: 无\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 微小 ~2KB\n## 关联: 本工具 -> onchainos_payment_create / onchainos_payment_verify", {}, { readOnlyHint: true }, async () => { if (!auth)
93
+ return AUTH_REQUIRED("READ"); try {
94
+ return toResult(await paymentsApi.supported(auth));
95
+ }
96
+ catch (e) {
97
+ return toError(e);
98
+ } });
99
+ }
100
+ //# sourceMappingURL=payments.js.map
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { Auth } from "../adapters/shared.js";
3
+ export declare function registerSkillTools(server: McpServer, auth: Auth | null): void;
4
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1,464 @@
1
+ /**
2
+ * Skills 模块 — CAT:[链上-Skill]
3
+ * API 组合技能: 将多个 API 调用编排为单步工具
4
+ * Phase 2: 交易全链路 / 风险检测链 / 智能滑点 / 信号聚合
5
+ */
6
+ import { z } from "zod";
7
+ import { tradeApi, gatewayApi, marketApi, } from "../adapters/onchainos.js";
8
+ import { toResult, AUTH_REQUIRED } from "../adapters/shared.js";
9
+ // ── 常量 ────────────────────────────────────────────────────
10
+ /** 主链币地址集合 — 这些地址代表原生代币,无需 approve */
11
+ const NATIVE_ADDR = new Set([
12
+ "",
13
+ "0x0000000000000000000000000000000000000000",
14
+ "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
15
+ "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
16
+ "11111111111111111111111111111111",
17
+ "0x2::sui::SUI",
18
+ "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c",
19
+ ]);
20
+ function isNative(address) {
21
+ return NATIVE_ADDR.has(address) || NATIVE_ADDR.has(address.toLowerCase());
22
+ }
23
+ /** 从 swap API 返回体中提取 calldata/to/value,兼容多种返回结构 */
24
+ function extractTxFields(raw) {
25
+ const tx = raw?.tx ?? raw;
26
+ return {
27
+ to: tx?.to ?? "",
28
+ data: tx?.data ?? "",
29
+ value: tx?.value ?? "0",
30
+ gasPrice: tx?.gasPrice ?? tx?.maxPriorityFeePerGas,
31
+ gas: tx?.gas,
32
+ };
33
+ }
34
+ // ═══════════════════════════════════════════════════════════════
35
+ // Skill 1: 交易全链路
36
+ // ═══════════════════════════════════════════════════════════════
37
+ export function registerSkillTools(server, auth) {
38
+ // ── 1. 交易全链路 ──────────────────────────────────────
39
+ server.tool("onchainos_skill_trade_pipeline", "CAT:[链上-Swap] | ## 功能: 全自动交易管线: 报价→(授权)→构建交易→模拟, 返回完整 calldata 和逐步骤结果\n## 场景: \"用1 SOL换USDC\" → 自动判断主链币省去授权, ERC20自动加approve\n## 关键词: pipeline, trade, 全链路, swap, 自动, simulate, approve\n## 参数:\n## - chainIndex/fromTokenAddress/toTokenAddress/amount/userWalletAddress(必填)\n## - slippagePercent: 滑点(可选,默认1.0)\n## - gasLevel: Gas等级(可选,默认average)\n## 鉴权: 需要 API Key(交易)\n## 风险: WRITE - 构建 calldata, 不上链\n## 返回量: 中等 ~15KB\n## 关联: 本工具产出 calldata → 用户签名 → onchainos_gateway_broadcast", {
40
+ chainIndex: z.string().describe("链索引。如 '1'=ETH '501'=Solana '8453'=Base。从 onchainos_dex_supported_chain 获取"),
41
+ fromTokenAddress: z.string().describe("卖出代币合约地址。主链币传空字符串''或'0xEeeeEeeeEeeeEeeeEeeeEeeeEeeeEeeeEeeeEEeE'"),
42
+ toTokenAddress: z.string().describe("买入代币合约地址"),
43
+ amount: z.string().describe("卖出数量(最小单位,含精度)。如 1 USDT=1000000, 1 SOL=1000000000"),
44
+ userWalletAddress: z.string().describe("用户钱包地址"),
45
+ slippagePercent: z.string().optional().default("1.0").describe("滑点百分比, 默认1.0。建议从 onchainos_skill_smart_slippage 获取"),
46
+ gasLevel: z.enum(["slow", "average", "fast"]).optional().describe("Gas等级, 默认average"),
47
+ }, { destructiveHint: true }, async (params) => {
48
+ if (!auth)
49
+ return AUTH_REQUIRED("TRADE");
50
+ const steps = [];
51
+ const warnings = [];
52
+ const { chainIndex, fromTokenAddress, toTokenAddress, amount, userWalletAddress, slippagePercent } = params;
53
+ // Step 1: Quote
54
+ try {
55
+ const qParams = {
56
+ chainIndex, fromTokenAddress, toTokenAddress, amount,
57
+ swapMode: "exactIn",
58
+ };
59
+ const quoteR = await tradeApi.quote(auth, qParams);
60
+ steps.push({ step: "quote", status: "ok", data: { priceImpactPercent: quoteR.priceImpactPercent, estimateGasFee: quoteR.estimateGasFee } });
61
+ }
62
+ catch (e) {
63
+ steps.push({ step: "quote", status: "error", error: e instanceof Error ? e.message : String(e) });
64
+ return toResult({ steps, summary: { status: "failed", failedAt: "quote", reason: "报价失败, 无法继续" } }, { warnings });
65
+ }
66
+ // Step 2: Approve (仅 ERC20 需要)
67
+ const needApprove = !isNative(fromTokenAddress);
68
+ let approveOk = false;
69
+ if (needApprove) {
70
+ try {
71
+ const approveR = await tradeApi.approveTransaction(auth, chainIndex, fromTokenAddress, amount);
72
+ steps.push({ step: "approve", status: "ok", data: { tokenContractAddress: fromTokenAddress, approveAmount: amount } });
73
+ approveOk = true;
74
+ }
75
+ catch (e) {
76
+ const msg = e instanceof Error ? e.message : String(e);
77
+ steps.push({ step: "approve", status: "error", error: msg });
78
+ warnings.push(`授权失败, 跳过授权继续: ${msg}`);
79
+ }
80
+ }
81
+ else {
82
+ steps.push({ step: "approve", status: "skipped", warning: "主链币无需授权" });
83
+ }
84
+ // Step 3: Build Swap
85
+ let swapRaw;
86
+ try {
87
+ const sParams = {
88
+ chainIndex, fromTokenAddress, toTokenAddress, amount,
89
+ userWalletAddress, slippagePercent: slippagePercent ?? "1.0",
90
+ };
91
+ if (params.gasLevel)
92
+ sParams.gasLevel = params.gasLevel;
93
+ if (approveOk) {
94
+ sParams.approveTransaction = "true";
95
+ sParams.approveAmount = amount;
96
+ }
97
+ swapRaw = await tradeApi.swap(auth, sParams);
98
+ const tx = extractTxFields(swapRaw);
99
+ steps.push({ step: "swap", status: "ok", data: { to: tx.to, dataLen: tx.data?.length ?? 0, value: tx.value, gasPrice: tx.gasPrice, gas: tx.gas } });
100
+ }
101
+ catch (e) {
102
+ steps.push({ step: "swap", status: "error", error: e instanceof Error ? e.message : String(e) });
103
+ return toResult({ steps, summary: { status: "failed", failedAt: "swap", reason: "构建交易失败, 无法继续" } }, { warnings });
104
+ }
105
+ // Step 4: Simulate
106
+ const swapTx = extractTxFields(swapRaw);
107
+ try {
108
+ const simR = await gatewayApi.simulate(auth, {
109
+ fromAddress: userWalletAddress,
110
+ toAddress: swapTx.to,
111
+ chainIndex,
112
+ txAmount: swapTx.value,
113
+ extJson: { inputData: swapTx.data },
114
+ });
115
+ steps.push({ step: "simulate", status: "ok", data: simR });
116
+ }
117
+ catch (e) {
118
+ const msg = e instanceof Error ? e.message : String(e);
119
+ steps.push({ step: "simulate", status: "error", error: msg });
120
+ warnings.push(`模拟执行失败: ${msg}。广播前请手动调用 onchainos_gateway_simulate 确认`);
121
+ }
122
+ const okCount = steps.filter(s => s.status === "ok" || s.status === "skipped").length;
123
+ const isReady = steps.every(s => s.status !== "error" || s.step === "approve");
124
+ const nextSteps = [
125
+ { action: "用户签名 calldata", tool: "—", condition: "用私钥/钱包对 swap 步骤返回的 data(calldata) 签名" },
126
+ ];
127
+ if (isReady) {
128
+ nextSteps.push({ action: "广播签名交易", tool: "onchainos_gateway_broadcast", params: { chainIndex, address: userWalletAddress } });
129
+ nextSteps.push({ action: "查交易状态", tool: "onchainos_gateway_orders", params: { address: userWalletAddress, chainIndex } });
130
+ }
131
+ return toResult({
132
+ steps,
133
+ calldata: { to: swapTx.to, data: swapTx.data, value: swapTx.value, gasPrice: swapTx.gasPrice, gas: swapTx.gas },
134
+ rawResponses: { swap: swapRaw },
135
+ summary: { status: isReady ? "ready" : "partial", totalSteps: steps.length, okSteps: okCount, failedSteps: steps.filter(s => s.status === "error").map(s => s.step) },
136
+ }, { warnings: warnings.length ? warnings : undefined, nextSteps });
137
+ });
138
+ // ── 2. 风险检测链 ──────────────────────────────────────
139
+ server.tool("onchainos_skill_risk_detect", "CAT:[链上-行情] | ## 功能: 综合风险检测: 安全分析+持仓集中度+打包检测+开发者信息, 输出0-100风险评分\n## 场景: 交易土狗/新代币前必调, 检测貔貅/Rug Pull/狙击手/打包\n## 关键词: risk, 风险, 安全, honeypot, 貔貅, rug, bundle, cluster, dev\n## 参数:\n## - chainIndex/tokenContractAddress(必填)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 中等 ~10KB\n## 关联: onchainos_token_search → 本工具 → onchainos_skill_trade_pipeline(低风险通过后)", {
140
+ chainIndex: z.string().describe("链索引。如 '501'=Solana '1'=ETH '8453'=Base"),
141
+ tokenContractAddress: z.string().describe("代币合约地址。从 onchainos_token_search 获取"),
142
+ }, { readOnlyHint: true }, async ({ chainIndex, tokenContractAddress }) => {
143
+ if (!auth)
144
+ return AUTH_REQUIRED("READ");
145
+ // 4 个 API 并行调用,互不依赖
146
+ const results = await Promise.allSettled([
147
+ marketApi.tokenAdvancedInfo(auth, chainIndex, tokenContractAddress)
148
+ .then(d => ({ step: "advanced_info", status: "ok", data: d }))
149
+ .catch(e => ({ step: "advanced_info", status: "error", error: e instanceof Error ? e.message : String(e) })),
150
+ marketApi.tokenClusterOverview(auth, chainIndex, tokenContractAddress)
151
+ .then(d => ({ step: "cluster_overview", status: "ok", data: d }))
152
+ .catch(e => ({ step: "cluster_overview", status: "error", error: e instanceof Error ? e.message : String(e) })),
153
+ marketApi.memepumpBundleInfo(auth, chainIndex, tokenContractAddress)
154
+ .then(d => ({ step: "bundle_info", status: "ok", data: d }))
155
+ .catch(e => ({ step: "bundle_info", status: "error", error: e instanceof Error ? e.message : String(e) })),
156
+ marketApi.memepumpTokenDevInfo(auth, chainIndex, tokenContractAddress)
157
+ .then(d => ({ step: "dev_info", status: "ok", data: d }))
158
+ .catch(e => ({ step: "dev_info", status: "error", error: e instanceof Error ? e.message : String(e) })),
159
+ ]);
160
+ const steps = [];
161
+ for (const r of results) {
162
+ if (r.status === "fulfilled")
163
+ steps.push(r.value);
164
+ else
165
+ steps.push({ step: "unknown", status: "error", error: r.reason?.message ?? String(r.reason) });
166
+ }
167
+ // ── 风险评分 ──────────────────────────────────────
168
+ let score = 0;
169
+ const factors = [];
170
+ const advInfo = steps.find(s => s.step === "advanced_info" && s.status === "ok")?.data;
171
+ const clustInfo = steps.find(s => s.step === "cluster_overview" && s.status === "ok")?.data;
172
+ const bundleInfo = steps.find(s => s.step === "bundle_info" && s.status === "ok")?.data;
173
+ const devInfo = steps.find(s => s.step === "dev_info" && s.status === "ok")?.data;
174
+ // Factor 1: HoneyPot (30 pts)
175
+ if (advInfo?.isHoneypot) {
176
+ score += 30;
177
+ factors.push("检测到貔貅(HoneyPot)");
178
+ }
179
+ if (advInfo?.isHoneyPot) {
180
+ score += 30;
181
+ factors.push("检测到貔貅(HoneyPot)");
182
+ }
183
+ // Factor 2: Risk Level (20 pts)
184
+ const riskLevel = (advInfo?.riskLevel ?? "").toUpperCase();
185
+ if (riskLevel === "HIGH" || riskLevel === "CRITICAL") {
186
+ score += 20;
187
+ factors.push(`风险等级=${riskLevel}`);
188
+ }
189
+ else if (riskLevel === "MEDIUM") {
190
+ score += 10;
191
+ factors.push(`风险等级=${riskLevel}`);
192
+ }
193
+ // Factor 3: Tax rate (10 pts — high buy/sell tax)
194
+ const buyTax = parseFloat(advInfo?.buyTax ?? "0");
195
+ const sellTax = parseFloat(advInfo?.sellTax ?? "0");
196
+ if (buyTax > 10 || sellTax > 10) {
197
+ score += 10;
198
+ factors.push(`高税率 buy=${buyTax}% sell=${sellTax}%`);
199
+ }
200
+ else if (buyTax > 5 || sellTax > 5) {
201
+ score += 5;
202
+ factors.push(`中等税率 buy=${buyTax}% sell=${sellTax}%`);
203
+ }
204
+ // Factor 4: Rug Pull % (15 pts)
205
+ const rugPercent = parseFloat(clustInfo?.rugPullPercent ?? "0");
206
+ if (rugPercent > 50) {
207
+ score += 15;
208
+ factors.push(`Rug概率>50%: ${rugPercent}%`);
209
+ }
210
+ else if (rugPercent > 25) {
211
+ score += 8;
212
+ factors.push(`Rug概率>25%: ${rugPercent}%`);
213
+ }
214
+ // Factor 5: Concentration (15 pts)
215
+ const concentration = (clustInfo?.clusterConcentration ?? "").toUpperCase();
216
+ if (concentration === "HIGH") {
217
+ score += 15;
218
+ factors.push("持仓高度集中");
219
+ }
220
+ else if (concentration === "MEDIUM") {
221
+ score += 7;
222
+ factors.push("持仓中度集中");
223
+ }
224
+ // Factor 6: Bundle detected (10 pts)
225
+ const hasBundle = bundleInfo && (bundleInfo.isBundled || (Array.isArray(bundleInfo.bundles) && bundleInfo.bundles.length > 0));
226
+ if (hasBundle) {
227
+ score += 10;
228
+ factors.push("检测到打包(Bundle)交易");
229
+ }
230
+ // Factor 7: Creator/dev history (10 pts)
231
+ const hasDevRisk = devInfo &&
232
+ (devInfo.isRugHistory || devInfo.hasRugHistory ||
233
+ parseFloat(devInfo.rugCount ?? "0") > 0 ||
234
+ devInfo.isRugDev === true);
235
+ if (hasDevRisk) {
236
+ score += 10;
237
+ factors.push("开发者有Rug历史");
238
+ }
239
+ score = Math.min(score, 100);
240
+ const level = score <= 20 ? "LOW" : score <= 50 ? "MEDIUM" : score <= 70 ? "HIGH" : "CRITICAL";
241
+ const nextSteps = [];
242
+ if (level === "LOW" || level === "MEDIUM") {
243
+ nextSteps.push({ action: "低/中风险, 可考虑交易", tool: "onchainos_skill_trade_pipeline", condition: "确认风险可接受后" });
244
+ }
245
+ else {
246
+ nextSteps.push({ action: "高风险, 强烈不建议交易", tool: "—", condition: "风险评分过高, 建议放弃" });
247
+ nextSteps.push({ action: "如仍需交易, 手动调小金额并设高滑点", tool: "onchainos_dex_swap", condition: "了解风险后自行决定" });
248
+ }
249
+ return toResult({
250
+ steps,
251
+ riskScore: { total: score, level, factors },
252
+ summary: {
253
+ status: level === "LOW" ? "safe" : level === "MEDIUM" ? "caution" : "danger",
254
+ recommendation: level === "LOW" ? "风险可控" : level === "MEDIUM" ? "谨慎操作" : level === "HIGH" ? "强烈不建议" : "禁止交易",
255
+ },
256
+ }, { nextSteps });
257
+ });
258
+ // ── 3. 智能滑点推荐 ────────────────────────────────────
259
+ server.tool("onchainos_skill_smart_slippage", "CAT:[链上-Swap] | ## 功能: 基于波动率和价格影响智能推荐最优滑点百分比\n## 场景: Agent 构建交易前获取推荐滑点, 避免因滑点太低被拒或太高被夹\n## 关键词: slippage, 滑点, 推荐, volatility, 波动率, price impact\n## 参数:\n## - chainIndex/fromTokenAddress/toTokenAddress(必填)\n## - amount: 交易数量(可选,用于评估价格影响)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 微小 ~3KB\n## 关联: 本工具 → onchainos_dex_swap / onchainos_skill_trade_pipeline(传 recommendedSlippagePercent)", {
260
+ chainIndex: z.string().describe("链索引。如 '501'=Solana '1'=ETH"),
261
+ fromTokenAddress: z.string().describe("卖出代币合约地址"),
262
+ toTokenAddress: z.string().describe("买入代币合约地址"),
263
+ amount: z.string().optional().describe("交易数量(最小单位)。填了会调 quote 评估价格影响, 不填仅用波动率估算"),
264
+ }, { readOnlyHint: true }, async ({ chainIndex, fromTokenAddress, toTokenAddress, amount }) => {
265
+ if (!auth)
266
+ return AUTH_REQUIRED("READ");
267
+ const steps = [];
268
+ const warnings = [];
269
+ // Step 1: 查报价获取价格影响
270
+ let priceImpact = 0;
271
+ if (amount) {
272
+ try {
273
+ const qParams = { chainIndex, fromTokenAddress, toTokenAddress, amount, swapMode: "exactIn" };
274
+ const quoteR = await tradeApi.quote(auth, qParams);
275
+ priceImpact = Math.abs(parseFloat(quoteR.priceImpactPercent ?? "0"));
276
+ steps.push({ step: "quote_impact", status: "ok", data: { priceImpactPercent: priceImpact } });
277
+ }
278
+ catch (e) {
279
+ const msg = e instanceof Error ? e.message : String(e);
280
+ steps.push({ step: "quote_impact", status: "error", error: msg });
281
+ warnings.push(`报价查询失败, 跳过价格影响评估: ${msg}`);
282
+ }
283
+ }
284
+ else {
285
+ steps.push({ step: "quote_impact", status: "skipped", warning: "未提供 amount, 跳过价格影响评估" });
286
+ }
287
+ // Step 2: 查 K 线计算波动率
288
+ let volatility = 0.5; // 默认中等波动
289
+ try {
290
+ const candleR = await marketApi.candles(auth, {
291
+ chainIndex, tokenContractAddress: fromTokenAddress,
292
+ bar: "1H", limit: "100",
293
+ });
294
+ const candles = Array.isArray(candleR) ? candleR : [];
295
+ if (candles.length >= 2) {
296
+ const closes = candles.map((c) => parseFloat(Array.isArray(c) ? c[4] : (c.close ?? c.c ?? "0"))).filter((v) => !isNaN(v) && v > 0);
297
+ if (closes.length >= 2) {
298
+ const mean = closes.reduce((a, b) => a + b, 0) / closes.length;
299
+ const variance = closes.reduce((s, c) => s + Math.pow(c - mean, 2), 0) / closes.length;
300
+ volatility = Math.sqrt(variance) / mean; // CV (coefficient of variation)
301
+ }
302
+ }
303
+ steps.push({ step: "volatility", status: "ok", data: { coefficientOfVariation: +volatility.toFixed(4), candleCount: candles.length } });
304
+ }
305
+ catch (e) {
306
+ const msg = e instanceof Error ? e.message : String(e);
307
+ steps.push({ step: "volatility", status: "error", error: msg });
308
+ warnings.push(`K线查询失败, 使用默认波动率 0.5: ${msg}`);
309
+ }
310
+ // Step 3: 计算推荐滑点
311
+ let baseSlippage = 3.0;
312
+ const vtiers = [[0.01, 0.3], [0.03, 0.5], [0.08, 1.0], [0.15, 2.0]];
313
+ for (const [maxV, slip] of vtiers) {
314
+ if (volatility <= maxV) {
315
+ baseSlippage = slip;
316
+ break;
317
+ }
318
+ }
319
+ let impactAdjust = 0;
320
+ if (priceImpact > 10)
321
+ impactAdjust = 2.0;
322
+ else if (priceImpact > 5)
323
+ impactAdjust = 1.0;
324
+ else if (priceImpact > 1)
325
+ impactAdjust = 0.5;
326
+ const recommended = Math.min(Math.max(baseSlippage + impactAdjust, 0.1), 15.0);
327
+ const conservative = Math.max(recommended * 0.5, 0.1);
328
+ const aggressive = Math.min(recommended * 2, 15.0);
329
+ return toResult({
330
+ steps,
331
+ recommendation: {
332
+ recommendedSlippagePercent: recommended.toFixed(1),
333
+ reason: [
334
+ `波动率 CV=${(volatility * 100).toFixed(1)}% → 基础滑点 ${baseSlippage}%`,
335
+ impactAdjust > 0 ? `价格影响 ${priceImpact.toFixed(1)}% → 额外 +${impactAdjust}%` : "",
336
+ ].filter(Boolean).join("; "),
337
+ factorsConsidered: {
338
+ volatilityPercent: +(volatility * 100).toFixed(2),
339
+ priceImpactPercent: priceImpact,
340
+ baseSlippagePercent: baseSlippage,
341
+ impactAdjustmentPercent: impactAdjust,
342
+ },
343
+ range: {
344
+ conservativePercent: conservative.toFixed(1),
345
+ recommendedPercent: recommended.toFixed(1),
346
+ aggressivePercent: aggressive.toFixed(1),
347
+ },
348
+ },
349
+ }, {
350
+ warnings: warnings.length ? warnings : undefined,
351
+ nextSteps: [
352
+ { action: "用推荐滑点构建交易", tool: "onchainos_skill_trade_pipeline", params: { chainIndex, fromTokenAddress, toTokenAddress, slippagePercent: recommended.toFixed(1) } },
353
+ { action: "或手动构建", tool: "onchainos_dex_swap", params: { chainIndex, fromTokenAddress, toTokenAddress, slippagePercent: recommended.toFixed(1) } },
354
+ ],
355
+ });
356
+ });
357
+ // ── 4. 信号聚合 ────────────────────────────────────────
358
+ server.tool("onchainos_skill_signal_aggregate", "CAT:[链上-信号] | ## 功能: 获取买入信号+风险过滤, 自动剔除貔貅/高风险代币, 返回带安全评级的精选信号\n## 场景: 跟踪聪明钱/KOL/鲸鱼动态, 一站式发现+过滤\n## 关键词: signal, 信号, 聪明钱, 聚合, 过滤, 风险, KOL, 鲸鱼\n## 参数:\n## - chainIndex(必填)\n## - walletType: 钱包类型过滤(可选)\n## - minAmountUsd/maxAmountUsd: 金额筛选(可选)\n## - limit: 返回条数(可选,默认10,最大20)\n## 鉴权: 需要 API Key(只读)\n## 风险: READ - 只读查询\n## 返回量: 中等 ~30KB\n## 关联: 本工具 → onchainos_skill_risk_detect(深入分析) → onchainos_skill_trade_pipeline(交易)", {
359
+ chainIndex: z.string().describe("链索引。如 '1'=ETH '501'=Solana。从 onchainos_signal_supported_chain 获取"),
360
+ walletType: z.string().optional().describe("钱包类型: '1'=聪明钱 '2'=KOL '3'=鲸鱼, 逗号分隔。不传不过滤"),
361
+ minAmountUsd: z.string().optional().describe("最小交易金额(USD)。如 '1000'=只返回>$1000的信号"),
362
+ maxAmountUsd: z.string().optional().describe("最大交易金额(USD)"),
363
+ limit: z.string().optional().default("10").describe("返回信号条数, 默认10, 最大20"),
364
+ }, { readOnlyHint: true }, async ({ chainIndex, walletType, minAmountUsd, maxAmountUsd, limit }) => {
365
+ if (!auth)
366
+ return AUTH_REQUIRED("READ");
367
+ const steps = [];
368
+ const limitNum = Math.min(parseInt(limit ?? "10", 10) || 10, 20);
369
+ // Step 1: 获取原始信号
370
+ let signals = [];
371
+ try {
372
+ const body = { chainIndex };
373
+ if (walletType)
374
+ body.walletType = walletType;
375
+ if (minAmountUsd)
376
+ body.minAmountUsd = minAmountUsd;
377
+ if (maxAmountUsd)
378
+ body.maxAmountUsd = maxAmountUsd;
379
+ body.limit = Math.min(limitNum * 3, 60); // 过度获取以便过滤后有足够数据
380
+ const raw = await marketApi.signalList(auth, body);
381
+ signals = (Array.isArray(raw) ? raw : raw?.dataList ?? raw?.signals ?? []);
382
+ steps.push({ step: "signal_list", status: "ok", data: { totalSignals: signals.length } });
383
+ }
384
+ catch (e) {
385
+ steps.push({ step: "signal_list", status: "error", error: e instanceof Error ? e.message : String(e) });
386
+ return toResult({ steps, signals: [], summary: { status: "failed", reason: "信号获取失败" } });
387
+ }
388
+ if (signals.length === 0) {
389
+ return toResult({ steps, signals: [], summary: { status: "empty", message: "当前无匹配信号" } });
390
+ }
391
+ // Step 2: 去重 token 地址
392
+ const tokenMap = new Map();
393
+ for (const sig of signals) {
394
+ const ci = sig.chainIndex ?? chainIndex;
395
+ const addr = sig.tokenContractAddress ?? sig.tokenAddress;
396
+ if (!addr)
397
+ continue;
398
+ const key = `${ci}:${addr}`;
399
+ if (!tokenMap.has(key))
400
+ tokenMap.set(key, { chainIndex: String(ci), tokenContractAddress: String(addr) });
401
+ }
402
+ const tokensToCheck = Array.from(tokenMap.values()).slice(0, limitNum);
403
+ const riskCache = {};
404
+ const chunkSize = 5;
405
+ for (let i = 0; i < tokensToCheck.length; i += chunkSize) {
406
+ const chunk = tokensToCheck.slice(i, i + chunkSize);
407
+ const chunkResults = await Promise.allSettled(chunk.map(async (t) => {
408
+ const info = await marketApi.tokenAdvancedInfo(auth, t.chainIndex, t.tokenContractAddress);
409
+ const key = `${t.chainIndex}:${t.tokenContractAddress}`;
410
+ return { key, info };
411
+ }));
412
+ for (const r of chunkResults) {
413
+ if (r.status === "fulfilled") {
414
+ riskCache[r.value.key] = r.value.info;
415
+ }
416
+ }
417
+ }
418
+ steps.push({ step: "risk_filter", status: "ok", data: { tokensChecked: tokensToCheck.length, riskDataAvailable: Object.keys(riskCache).length } });
419
+ // Step 4: 过滤 + 排序
420
+ const enriched = signals
421
+ .map(sig => {
422
+ const ci = sig.chainIndex ?? chainIndex;
423
+ const addr = sig.tokenContractAddress ?? sig.tokenAddress ?? "";
424
+ const key = `${ci}:${addr}`;
425
+ const risk = riskCache[key];
426
+ // 过滤明显风险
427
+ if (risk) {
428
+ const isHoney = risk.isHoneypot || risk.isHoneyPot;
429
+ const rl = (risk.riskLevel ?? "").toUpperCase();
430
+ if (isHoney || rl === "HIGH" || rl === "CRITICAL")
431
+ return null;
432
+ }
433
+ return {
434
+ chainIndex: String(ci),
435
+ tokenContractAddress: String(addr),
436
+ tokenSymbol: sig.tokenSymbol ?? sig.symbol,
437
+ walletAddress: sig.walletAddress ?? sig.address,
438
+ walletType: sig.walletType ?? walletType,
439
+ amountUsd: sig.amountUsd ?? sig.amountUSD,
440
+ timestamp: sig.timestamp ?? sig.ts,
441
+ riskContext: risk
442
+ ? { riskLevel: risk.riskLevel, isHoneypot: risk.isHoneypot || risk.isHoneyPot, buyTax: risk.buyTax, sellTax: risk.sellTax }
443
+ : { warning: "风险数据未获取" },
444
+ };
445
+ })
446
+ .filter(Boolean)
447
+ .slice(0, limitNum);
448
+ return toResult({
449
+ steps,
450
+ signals: enriched,
451
+ summary: {
452
+ status: enriched.length > 0 ? "ready" : "filtered_empty",
453
+ totalRaw: signals.length,
454
+ afterFilter: enriched.length,
455
+ riskFiltered: signals.length - enriched.length,
456
+ },
457
+ }, {
458
+ nextSteps: enriched.length > 0
459
+ ? [{ action: "深入分析特定代币", tool: "onchainos_skill_risk_detect", params: { chainIndex: enriched[0]?.chainIndex, tokenContractAddress: "{{选中代币地址}}" } }]
460
+ : [{ action: "无安全信号, 放宽条件或检查其他链", tool: "onchainos_signal_list" }],
461
+ });
462
+ });
463
+ }
464
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { Auth } from "../adapters/shared.js";
3
+ export declare function registerTradeTools(server: McpServer, auth: Auth | null): void;
4
+ //# sourceMappingURL=trade.d.ts.map