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
package/docs/TRADE.md ADDED
@@ -0,0 +1,103 @@
1
+ # Trade API — 知识总结
2
+
3
+ > 来源: web3.okx.com 官方教程 · 对接日期 2026-06-14
4
+
5
+ ---
6
+
7
+ ## 经典兑换 API (8 endpoints, v6, 全部 GET)
8
+
9
+ 从 Solana/EVM/Sui/Ton 四个教程提取:
10
+
11
+ | # | 端点 | 关键参数 |
12
+ |---|------|---------|
13
+ | 1 | `GET /api/v6/dex/aggregator/supported/chain` | 无 |
14
+ | 2 | `GET /api/v6/dex/aggregator/all-tokens?chainIndex=` | chainIndex(String) |
15
+ | 3 | `GET /api/v6/dex/aggregator/get-liquidity?chainIndex=` | chainIndex(String) |
16
+ | 4 | `GET /api/v6/dex/aggregator/approve-transaction` | chainIndex, tokenContractAddress, approveAmount |
17
+ | 5 | `GET /api/v6/dex/aggregator/quote` | chainIndex, fromTokenAddress, toTokenAddress, amount, slippagePercent |
18
+ | 6 | `GET /api/v6/dex/aggregator/swap` | chainIndex, fromTokenAddress, toTokenAddress, amount, userWalletAddress, slippagePercent, autoSlippage, maxAutoSlippagePercent |
19
+ | 7 | `GET /api/v6/dex/aggregator/swap-instruction` | chainIndex, fromTokenAddress, toTokenAddress, amount, slippagePercent, userWalletAddress, feePercent, autoSlippage, fromTokenReferrerWalletAddress, pathNum |
20
+ | 8 | `GET /api/v6/dex/aggregator/history` | chainIndex, txHash, isFromMyProject |
21
+
22
+ **关键**: chainIndex 全部是 **String** 类型 (如 "501", "8453", "1", "784", "607")
23
+
24
+ ## 兑换流程 (Agent 标准路径)
25
+
26
+ ```
27
+ 1. supported/chain → 确认链可用
28
+ 2. all-tokens / get-liquidity → 确认代币和流动性
29
+ 3. quote → 拿报价 (输出金额, 价格影响, 路由)
30
+ 4. approve-transaction → (仅 ERC-20) 授权 DEX 合约
31
+ 5. swap → 构建交易 calldata
32
+ 6. simulate (Gateway) → 模拟执行
33
+ 7. broadcast (Gateway) → 广播已签名交易
34
+ 8. history / orders → 追踪状态
35
+ ```
36
+
37
+ ## DEX SDK 接口参数 (Phase 2 参考)
38
+
39
+ ### getQuote / executeSwap 通用参数
40
+
41
+ | 参数 | 类型 | 说明 |
42
+ |------|------|------|
43
+ | chainIndex | String | 链ID |
44
+ | fromTokenAddress | String | 卖出代币 |
45
+ | toTokenAddress | String | 买入代币 |
46
+ | amount | String | 数量(人类可读, SDK 内部转 base units) |
47
+ | slippagePercent | String | 滑点, 如 "0.5" |
48
+ | userWalletAddress | String | 钱包地址 |
49
+
50
+ ### executeApproval 参数 (EVM)
51
+
52
+ | 参数 | 说明 |
53
+ |------|------|
54
+ | chainIndex / tokenContractAddress / approveAmount | 标准授权参数 |
55
+
56
+ ### swap-instruction 额外参数 (Solana 高级)
57
+
58
+ | 参数 | 说明 |
59
+ |------|------|
60
+ | feePercent | 分佣百分比, 如 "1" |
61
+ | autoSlippage | "true"/"false" |
62
+ | fromTokenReferrerWalletAddress | 推荐费地址 |
63
+ | pathNum | 最大路由数, 如 "3" |
64
+
65
+ 返回: `{ instructionLists, addressLookupTableAccount }`
66
+
67
+ ### 各链常量速查
68
+
69
+ | 链 | chainIndex | 原生代币 |
70
+ |----|-----------|---------|
71
+ | Solana | "501" | `11111111111111111111111111111111` |
72
+ | Sui | "784" | `0x2::sui::SUI` |
73
+ | Base | "8453" | `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` |
74
+ | Ton | "607" | `EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c` |
75
+ | BSC | "56" | `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` |
76
+
77
+ ## DEX Router 合约地址 (来源: 智能合约页面)
78
+
79
+ | 链 | Router | Approve |
80
+ |----|--------|---------|
81
+ | ETH | `0x28b1Dc1a5E3699A428BC51d234DFab7C9CB2a183` | `0x40aA958dd87FC8305b97f2BA922CDdCa374bcD7f` |
82
+ | Solana | `proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u` | 不需要 |
83
+ | Base | `0xC8F6b8Ba0DC0f175B568B99440B0867F69A29265` | `0x57df6092665eb6058DE53939612413ff4B09114E` |
84
+ | BSC | `0x62cceF0b4545166f721cAa9fEe13c1d3767E27dc` | `0x2c34A2Fb1d0b4f55de51E1d0bDEfaDDce6b7cDD6` |
85
+ | Arbitrum | `0x7CF6b330b437E9fb432B1400DE17B03357Cf049A` | `0x70cBb871E8f30Fc8Ce23609E9E0Ea87B6b222F58` |
86
+ | X Layer | `0x722db4f285F8bD91ef7AF6DA397e83f7fA4E80a7` | `0x8b773D83bc66Be128c60e07E17C8901f7a64F000` |
87
+ | Sui | `0x4f1f29379f9fff73adb850ecf15513179d9b6a924e8c1d553d25582629778923` | 不需要 |
88
+ | Ton | `EQAgvOlWk7C0Pz3YgSaX-MA7UDDhE9n6eQgQRwJahOBm4VKr` | 不需要 |
89
+
90
+ 完整列表见 docs/TRADE.md 源码
91
+
92
+ ## 做市商接入 / Solver 接入 — 反向集成
93
+
94
+ 做市商(PMM): OKX GET → 你的 `/OKXDEX/rfq/pricing` + POST → 你的 `/OKXDEX/rfq/firm-order`
95
+ Solver: OKX POST → 你的 `/OKXDEX/intent/quote` + `/solve` + `/settle` + `/notify`
96
+
97
+ 均非 MCP Tool 可封装的 REST API。需自行实现端点供 OKX 调用。
98
+
99
+ ## 未对接模块
100
+
101
+ - **意图兑换 API** (8 endpoints) — 官方 API 参考页面未贴
102
+ - **意图交易 Solver** (7 endpoints) — 官方 API 参考页面未贴
103
+ - **做市商接入** — SDK 反向集成, 非 REST
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "hchain-mcp",
3
+ "version": "1.0.0",
4
+ "description": "h-mcp — AI-native multi-chain trading MCP Server, 99 tools over HTTP & stdio",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "start": "node dist/index.js",
11
+ "start:http": "node dist/http.js",
12
+ "test": "npx tsx --test src/**/*.test.ts"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.8.0",
16
+ "dotenv": "^17.4.2",
17
+ "express": "^5.2.1",
18
+ "zod": "^3.24.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/express": "^5.0.6",
22
+ "@types/node": "^22.0.0",
23
+ "typescript": "^5.7.0"
24
+ }
25
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Onchain OS WebSocket 适配层
3
+ * wss://wsdex.okx.com/ws/v6/dex
4
+ *
5
+ * MCP Tool 限制: WS 是长连接推送, Tool 是请求-响应。
6
+ * 工具只管理连接生命周期 (subscribe/unsubscribe), 推送数据走 stderr 日志。
7
+ */
8
+ import type { Auth } from "./shared.js";
9
+ import crypto from "node:crypto";
10
+
11
+ const WS_URL = "wss://wsdex.okx.com/ws/v6/dex";
12
+ const PING_INTERVAL = 25000;
13
+
14
+ // ── WebSocket Manager ─────────────────────────────────────
15
+
16
+ type WSChannel =
17
+ | { channel: "price"; chainIndex: string; tokenContractAddress: string }
18
+ | { channel: "price-info"; chainIndex: string; tokenContractAddress: string }
19
+ | { channel: "trades"; chainIndex: string; tokenContractAddress: string }
20
+ | { channel: string; chainIndex: string; [key: string]: string };
21
+
22
+ interface WSState { ws: WebSocket | null; connId: string | null; subscribed: Set<string>; }
23
+
24
+ const state: WSState = { ws: null, connId: null, subscribed: new Set() };
25
+
26
+ function channelKey(c: WSChannel): string { return `${c.channel}:${c.chainIndex}:${"tokenContractAddress" in c ? c.tokenContractAddress : ""}`; }
27
+
28
+ function sign(secret: string, timestamp: string): string {
29
+ return crypto.createHmac("sha256", secret).update(timestamp + "GET" + "/users/self/verify").digest("base64");
30
+ }
31
+
32
+ function heartbeat(ws: WebSocket) {
33
+ const timer = setInterval(() => { if (ws.readyState === WebSocket.OPEN) ws.send("ping"); }, PING_INTERVAL);
34
+ ws.addEventListener("close", () => clearInterval(timer));
35
+ }
36
+
37
+ // ── Public API ──────────────────────────────────────────
38
+
39
+ export async function wsConnect(auth: Auth): Promise<string> {
40
+ if (state.ws && state.ws.readyState === WebSocket.OPEN) return state.connId ?? "already-connected";
41
+
42
+ const timestamp = String(Math.floor(Date.now() / 1000));
43
+ const loginMsg = JSON.stringify({
44
+ op: "login",
45
+ args: [{ apiKey: auth.apiKey, passphrase: auth.passphrase, timestamp, sign: sign(auth.secret, timestamp) }],
46
+ });
47
+
48
+ return new Promise((resolve, reject) => {
49
+ const ws = new WebSocket(WS_URL);
50
+ const timeout = setTimeout(() => { ws.close(); reject(new Error("WS connection timeout")); }, 10000);
51
+
52
+ ws.addEventListener("open", () => {
53
+ ws.send(loginMsg);
54
+ });
55
+
56
+ ws.addEventListener("message", (e: MessageEvent) => {
57
+ const data = JSON.parse(e.data as string);
58
+ if (data.event === "login" && data.code === "0") {
59
+ clearTimeout(timeout);
60
+ state.ws = ws;
61
+ state.connId = data.connId;
62
+ heartbeat(ws);
63
+ ws.addEventListener("message", (e2: MessageEvent) => {
64
+ const d = JSON.parse(e2.data as string);
65
+ if (d.event !== "login" && d.event !== "subscribe" && d.event !== "unsubscribe") {
66
+ console.error("[WS-DATA]", JSON.stringify(d));
67
+ }
68
+ });
69
+ resolve(data.connId);
70
+ } else if (data.event === "error") {
71
+ clearTimeout(timeout);
72
+ ws.close();
73
+ reject(new Error(`WS login failed: ${data.code} ${data.msg}`));
74
+ }
75
+ });
76
+
77
+ ws.addEventListener("error", (e) => { clearTimeout(timeout); reject(new Error(`WS error: ${JSON.stringify(e)}`)); });
78
+ });
79
+ }
80
+
81
+ export async function wsSubscribe(channel: WSChannel): Promise<void> {
82
+ if (!state.ws || state.ws.readyState !== WebSocket.OPEN) throw new Error("WS not connected, call ws_connect first");
83
+
84
+ const key = channelKey(channel);
85
+ if (state.subscribed.has(key)) return;
86
+
87
+ return new Promise((resolve, reject) => {
88
+ const handler = (e: MessageEvent) => {
89
+ const d = JSON.parse(e.data as string);
90
+ if (d.event === "subscribe" && d.arg?.channel === channel.channel) {
91
+ state.subscribed.add(key);
92
+ state.ws!.removeEventListener("message", handler);
93
+ resolve();
94
+ } else if (d.event === "error") {
95
+ state.ws!.removeEventListener("message", handler);
96
+ reject(new Error(`WS subscribe failed: ${d.code} ${d.msg}`));
97
+ }
98
+ };
99
+ state.ws!.addEventListener("message", handler);
100
+ state.ws!.send(JSON.stringify({ op: "subscribe", args: [channel] }));
101
+ });
102
+ }
103
+
104
+ export async function wsUnsubscribe(channel: WSChannel): Promise<void> {
105
+ if (!state.ws || state.ws.readyState !== WebSocket.OPEN) return;
106
+
107
+ const key = channelKey(channel);
108
+ if (!state.subscribed.has(key)) return;
109
+
110
+ return new Promise((resolve) => {
111
+ const handler = (e: MessageEvent) => {
112
+ const d = JSON.parse(e.data as string);
113
+ if (d.event === "unsubscribe") {
114
+ state.subscribed.delete(key);
115
+ state.ws!.removeEventListener("message", handler);
116
+ resolve();
117
+ }
118
+ };
119
+ state.ws!.addEventListener("message", handler);
120
+ state.ws!.send(JSON.stringify({ op: "unsubscribe", args: [channel] }));
121
+ });
122
+ }
123
+
124
+ export function wsDisconnect(): void {
125
+ if (state.ws) { state.ws.close(); state.ws = null; state.connId = null; state.subscribed.clear(); }
126
+ }