@michaleffffff/mcp-trading-server 3.0.4 → 3.0.5

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 (53) hide show
  1. package/CHANGELOG.md +18 -6
  2. package/README.md +48 -414
  3. package/TOOL_EXAMPLES.md +63 -559
  4. package/dist/prompts/tradingGuide.js +16 -23
  5. package/dist/server.js +20 -2
  6. package/dist/services/balanceService.js +4 -4
  7. package/dist/services/marketService.js +48 -12
  8. package/dist/services/tradeService.js +33 -10
  9. package/dist/tools/accountInfo.js +1 -82
  10. package/dist/tools/accountTransfer.js +2 -2
  11. package/dist/tools/adjustMargin.js +1 -1
  12. package/dist/tools/cancelOrders.js +71 -0
  13. package/dist/tools/checkAccountReady.js +69 -0
  14. package/dist/tools/checkApproval.js +1 -1
  15. package/dist/tools/closeAllPositions.js +9 -7
  16. package/dist/tools/closePosition.js +1 -1
  17. package/dist/tools/createPerpMarket.js +1 -1
  18. package/dist/tools/executeTrade.js +6 -6
  19. package/dist/tools/findPool.js +26 -0
  20. package/dist/tools/getAccountSnapshot.js +41 -0
  21. package/dist/tools/getAllTickers.js +5 -4
  22. package/dist/tools/getBaseDetail.js +1 -1
  23. package/dist/tools/getKline.js +7 -4
  24. package/dist/tools/getMyLpHoldings.js +3 -2
  25. package/dist/tools/getNetworkFee.js +1 -1
  26. package/dist/tools/getOrders.js +46 -0
  27. package/dist/tools/getPoolMetadata.js +83 -0
  28. package/dist/tools/getPositionsAll.js +80 -0
  29. package/dist/tools/{getMarketPrice.js → getPrice.js} +8 -5
  30. package/dist/tools/getUserTradingFeeRate.js +1 -1
  31. package/dist/tools/index.js +15 -19
  32. package/dist/tools/listPools.js +53 -0
  33. package/dist/tools/manageLiquidity.js +4 -4
  34. package/dist/tools/manageTpSl.js +163 -0
  35. package/dist/tools/openPositionSimple.js +22 -5
  36. package/dist/tools/searchTools.js +35 -0
  37. package/dist/utils/mappings.js +10 -7
  38. package/package.json +1 -1
  39. package/dist/tools/cancelAllOrders.js +0 -57
  40. package/dist/tools/cancelOrder.js +0 -22
  41. package/dist/tools/getAccountVipInfo.js +0 -20
  42. package/dist/tools/getKlineLatestBar.js +0 -28
  43. package/dist/tools/getOraclePrice.js +0 -22
  44. package/dist/tools/getPoolList.js +0 -17
  45. package/dist/tools/getPoolSymbolAll.js +0 -16
  46. package/dist/tools/getPositions.js +0 -77
  47. package/dist/tools/marketInfo.js +0 -88
  48. package/dist/tools/orderQueries.js +0 -51
  49. package/dist/tools/poolConfig.js +0 -22
  50. package/dist/tools/positionHistory.js +0 -28
  51. package/dist/tools/searchMarket.js +0 -21
  52. package/dist/tools/setTpSl.js +0 -50
  53. package/dist/tools/updateOrderTpSl.js +0 -34
@@ -0,0 +1,53 @@
1
+ import { resolveClient } from "../auth/resolveClient.js";
2
+ import { getPoolList } from "../services/marketService.js";
3
+ function collectRows(input) {
4
+ if (Array.isArray(input))
5
+ return input.flatMap(collectRows);
6
+ if (!input || typeof input !== "object")
7
+ return [];
8
+ if (input.poolId || input.pool_id)
9
+ return [input];
10
+ return Object.values(input).flatMap(collectRows);
11
+ }
12
+ export const listPoolsTool = {
13
+ name: "list_pools",
14
+ description: "[MARKET] Get the complete list of all available tradable pools, including symbol and icon metadata.",
15
+ schema: {},
16
+ handler: async () => {
17
+ try {
18
+ const { client } = await resolveClient();
19
+ // Fetch both list and symbols
20
+ const [poolListRes, symbolsRes] = await Promise.all([
21
+ getPoolList(client),
22
+ client.markets.getPoolSymbolAll().catch(() => ({ data: [] }))
23
+ ]);
24
+ const poolsRaw = collectRows(poolListRes?.data ?? poolListRes);
25
+ const symbolsRaw = collectRows(symbolsRes?.data ?? symbolsRes);
26
+ const symbolMap = new Map(symbolsRaw
27
+ .filter((row) => row?.poolId || row?.pool_id)
28
+ .map((s) => [String(s.poolId ?? s.pool_id).toLowerCase(), s]));
29
+ const deduped = new Map();
30
+ for (const row of poolsRaw) {
31
+ const poolId = String(row?.poolId ?? row?.pool_id ?? "").trim().toLowerCase();
32
+ if (!poolId)
33
+ continue;
34
+ if (!deduped.has(poolId)) {
35
+ deduped.set(poolId, row);
36
+ }
37
+ }
38
+ const enriched = Array.from(deduped.values()).map(pool => {
39
+ const poolId = String(pool.poolId ?? pool.pool_id ?? "").toLowerCase();
40
+ const symbolData = symbolMap.get(poolId);
41
+ return {
42
+ ...pool,
43
+ icon: symbolData?.icon || null,
44
+ symbolName: symbolData?.symbolName || pool.symbolName || pool.baseQuoteSymbol || pool.symbol || null,
45
+ };
46
+ });
47
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: enriched }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
48
+ }
49
+ catch (error) {
50
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
51
+ }
52
+ },
53
+ };
@@ -47,7 +47,7 @@ function resolveLpAssetNames(detail) {
47
47
  }
48
48
  export const manageLiquidityTool = {
49
49
  name: "manage_liquidity",
50
- description: "Add or withdraw liquidity from a BASE or QUOTE pool. Success response includes LP naming metadata: base `mBASE.QUOTE`, quote `mQUOTE.BASE`, plus `operatedLpAssetName` based on poolType.",
50
+ description: "[LIQUIDITY] Add or withdraw liquidity from a BASE or QUOTE pool. Success response includes LP naming metadata: base `mBASE.QUOTE`, quote `mQUOTE.BASE`, plus `operatedLpAssetName` based on poolType.",
51
51
  schema: {
52
52
  action: z.coerce.string().describe("'deposit' or 'withdraw' (aliases: add/remove/increase/decrease; case-insensitive)"),
53
53
  poolType: z.enum(["BASE", "QUOTE"]).describe("'BASE' or 'QUOTE'"),
@@ -79,7 +79,7 @@ export const manageLiquidityTool = {
79
79
  const detail = detailRes?.data || (detailRes?.marketId ? detailRes : null);
80
80
  if (!detail?.marketId) {
81
81
  throw new Error(`Pool ${poolId} not found on chainId ${chainId}. ` +
82
- `Please query a valid active pool via search_market/get_pool_list first.`);
82
+ `Please query a valid active pool via find_pool/list_pools first.`);
83
83
  }
84
84
  let raw;
85
85
  if (poolType === "QUOTE") {
@@ -93,7 +93,7 @@ export const manageLiquidityTool = {
93
93
  : await baseWithdraw(poolId, amount, slippage, chainId);
94
94
  }
95
95
  if (!raw) {
96
- throw new Error(`SDK returned an empty result for liquidity ${action}. This usually occurs if the pool is not in an Active state (state: 2) or if there is a contract-level restriction. Please check pool_info.`);
96
+ throw new Error(`SDK returned an empty result for liquidity ${action}. This usually occurs if the pool is not in an Active state (state: 2) or if there is a contract-level restriction. Please check get_pool_metadata.`);
97
97
  }
98
98
  if (raw && typeof raw === "object" && "code" in raw && Number(raw.code) !== 0) {
99
99
  throw new Error(`Liquidity ${action} failed: ${extractErrorMessage(raw)}`);
@@ -127,7 +127,7 @@ export const manageLiquidityTool = {
127
127
  };
128
128
  export const getLpPriceTool = {
129
129
  name: "get_lp_price",
130
- description: "Get the current internal net asset value (NAV) price of an LP token for a BASE or QUOTE pool. Note: This is NOT the underlying token's external Oracle market price (e.g. WETH's price), but rather the internal exchange rate / net worth of the LP token itself which fluctuates based on pool PnL and fees.",
130
+ description: "[LIQUIDITY] Get the current internal net asset value (NAV) price of an LP token for a BASE or QUOTE pool. Note: This is NOT the underlying token's external Oracle market price (e.g. WETH's price), but rather the internal exchange rate / net worth of the LP token itself which fluctuates based on pool PnL and fees.",
131
131
  schema: {
132
132
  poolType: z.enum(["BASE", "QUOTE"]).describe("'BASE' or 'QUOTE'"),
133
133
  poolId: z.string().describe("Pool ID"),
@@ -0,0 +1,163 @@
1
+ import { z } from "zod";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
+ import { finalizeMutationResult } from "../utils/mutationResult.js";
4
+ function normalizeDirectionInput(value) {
5
+ if (value === undefined || value === null || value === "")
6
+ return undefined;
7
+ if (typeof value === "number") {
8
+ if (value === 0 || value === 1)
9
+ return value;
10
+ throw new Error("direction must be LONG/SHORT or 0/1.");
11
+ }
12
+ const text = String(value).trim().toUpperCase();
13
+ if (text === "0" || text === "LONG" || text === "BUY")
14
+ return 0;
15
+ if (text === "1" || text === "SHORT" || text === "SELL")
16
+ return 1;
17
+ throw new Error("direction must be LONG/SHORT or 0/1.");
18
+ }
19
+ function isNonEmpty(value) {
20
+ return value !== undefined && value !== null && String(value).trim().length > 0;
21
+ }
22
+ function readSnapshotText(snapshot, keys) {
23
+ for (const key of keys) {
24
+ const value = snapshot?.[key];
25
+ if (isNonEmpty(value))
26
+ return String(value).trim();
27
+ }
28
+ return "";
29
+ }
30
+ function normalizeUnitsInput(text) {
31
+ const raw = String(text ?? "").trim();
32
+ if (!raw)
33
+ return "";
34
+ if (/^(raw|human):/i.test(raw))
35
+ return raw;
36
+ if (/^\d+$/.test(raw))
37
+ return `raw:${raw}`;
38
+ return raw;
39
+ }
40
+ async function findOrderSnapshot(client, address, chainId, orderId, poolId) {
41
+ const target = String(orderId).toLowerCase();
42
+ try {
43
+ const openRes = await client.order.getOrders(address);
44
+ const openOrders = Array.isArray(openRes?.data) ? openRes.data : [];
45
+ const found = openOrders.find((order) => String(order?.orderId ?? order?.id ?? "").toLowerCase() === target);
46
+ if (found)
47
+ return found;
48
+ }
49
+ catch {
50
+ }
51
+ try {
52
+ const historyQuery = { chainId, limit: 50, page: 1 };
53
+ if (poolId)
54
+ historyQuery.poolId = poolId;
55
+ const historyRes = await client.order.getOrderHistory(historyQuery, address);
56
+ const historyOrders = Array.isArray(historyRes?.data) ? historyRes.data : [];
57
+ return historyOrders.find((order) => String(order?.orderId ?? order?.id ?? "").toLowerCase() === target) ?? null;
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ export const manageTpSlTool = {
64
+ name: "manage_tp_sl",
65
+ description: "[TRADE] Set or update Take Profit (TP) and Stop Loss (SL) for a position.",
66
+ schema: {
67
+ poolId: z.string().describe("Pool ID"),
68
+ positionId: z.string().optional().describe("Position ID (required for creating TP/SL on a position)"),
69
+ orderId: z.string().optional().describe("Existing TP/SL Order ID to update (optional)"),
70
+ size: z.union([z.string(), z.number()]).optional().describe("Order size (required for update if cannot be auto-resolved from order snapshot)"),
71
+ price: z.union([z.string(), z.number()]).optional().describe("Order price (required for update if cannot be auto-resolved from order snapshot)"),
72
+ tpPrice: z.union([z.string(), z.number()]).optional().describe("New TP price"),
73
+ tpSize: z.union([z.string(), z.number()]).optional().describe("New TP size"),
74
+ slPrice: z.union([z.string(), z.number()]).optional().describe("New SL price"),
75
+ slSize: z.union([z.string(), z.number()]).optional().describe("New SL size"),
76
+ direction: z.union([z.enum(["LONG", "SHORT"]), z.number().int()]).optional().describe("Position direction (LONG/SHORT or 0/1; required for new TP/SL)"),
77
+ leverage: z.number().optional().describe("Position leverage"),
78
+ executionFeeToken: z.string().optional().describe("Fee token address"),
79
+ slippagePct: z.union([z.string(), z.number()]).optional().describe("Slippage in 4dp raw units. Default 100 (1%)."),
80
+ useOrderCollateral: z.boolean().optional().describe("Whether updateOrderTpSl should use order collateral (default true)."),
81
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
82
+ },
83
+ handler: async (args) => {
84
+ try {
85
+ const { client, address, signer } = await resolveClient();
86
+ const chainId = args.chainId ?? getChainId();
87
+ const direction = normalizeDirectionInput(args.direction);
88
+ const slippagePct = args.slippagePct ?? "100";
89
+ const { setPositionTpSl, updateOrderTpSl } = await import("../services/tradeService.js");
90
+ let raw;
91
+ if (args.orderId) {
92
+ // Update existing
93
+ const { getMarketDetail } = await import("../services/marketService.js");
94
+ const marketRes = await getMarketDetail(client, args.poolId, chainId);
95
+ const market = marketRes.data ?? marketRes;
96
+ const orderSnapshot = await findOrderSnapshot(client, address, chainId, args.orderId, args.poolId);
97
+ const snapshotSize = readSnapshotText(orderSnapshot, ["size", "orderSize", "positionSize"]);
98
+ const snapshotPrice = readSnapshotText(orderSnapshot, ["price", "orderPrice", "triggerPrice"]);
99
+ const snapshotTpPrice = readSnapshotText(orderSnapshot, ["tpPrice", "takeProfitPrice", "tpTriggerPrice"]);
100
+ const snapshotTpSize = readSnapshotText(orderSnapshot, ["tpSize", "takeProfitSize"]);
101
+ const snapshotSlPrice = readSnapshotText(orderSnapshot, ["slPrice", "stopLossPrice", "slTriggerPrice"]);
102
+ const snapshotSlSize = readSnapshotText(orderSnapshot, ["slSize", "stopLossSize"]);
103
+ const size = isNonEmpty(args.size) ? String(args.size) : snapshotSize;
104
+ const price = isNonEmpty(args.price) ? String(args.price) : snapshotPrice;
105
+ const tpPrice = isNonEmpty(args.tpPrice) ? String(args.tpPrice) : snapshotTpPrice;
106
+ const tpSize = isNonEmpty(args.tpSize) ? String(args.tpSize) : snapshotTpSize;
107
+ const slPrice = isNonEmpty(args.slPrice) ? String(args.slPrice) : snapshotSlPrice;
108
+ const slSize = isNonEmpty(args.slSize) ? String(args.slSize) : snapshotSlSize;
109
+ if (!size || !price) {
110
+ throw new Error("size and price are required for update. Provide them explicitly, or ensure orderId can be found via get_orders so they can be auto-resolved.");
111
+ }
112
+ if (!tpPrice && !slPrice) {
113
+ throw new Error("At least one of tpPrice or slPrice is required when updating TP/SL.");
114
+ }
115
+ if ((tpPrice && !tpSize) || (!tpPrice && tpSize)) {
116
+ throw new Error("TP update requires both tpPrice and tpSize (or resolvable existing TP fields).");
117
+ }
118
+ if ((slPrice && !slSize) || (!slPrice && slSize)) {
119
+ throw new Error("SL update requires both slPrice and slSize (or resolvable existing SL fields).");
120
+ }
121
+ raw = await updateOrderTpSl(client, address, {
122
+ orderId: args.orderId,
123
+ marketId: market.marketId,
124
+ poolId: args.poolId,
125
+ size: normalizeUnitsInput(size),
126
+ price: normalizeUnitsInput(price),
127
+ tpPrice: tpPrice ? normalizeUnitsInput(tpPrice) : "0",
128
+ tpSize: tpSize ? normalizeUnitsInput(tpSize) : "0",
129
+ slPrice: slPrice ? normalizeUnitsInput(slPrice) : "0",
130
+ slSize: slSize ? normalizeUnitsInput(slSize) : "0",
131
+ quoteToken: market.quoteToken,
132
+ useOrderCollateral: args.useOrderCollateral ?? true
133
+ }, chainId);
134
+ }
135
+ else {
136
+ // Create new
137
+ if (direction === undefined || !args.leverage) {
138
+ throw new Error("direction and leverage are required when creating new TP/SL.");
139
+ }
140
+ if (!args.positionId) {
141
+ throw new Error("positionId is required when creating new TP/SL.");
142
+ }
143
+ raw = await setPositionTpSl(client, address, {
144
+ poolId: args.poolId,
145
+ positionId: args.positionId,
146
+ direction,
147
+ leverage: args.leverage,
148
+ executionFeeToken: args.executionFeeToken,
149
+ slippagePct,
150
+ tpPrice: args.tpPrice,
151
+ tpSize: args.tpSize,
152
+ slPrice: args.slPrice,
153
+ slSize: args.slSize
154
+ }, chainId);
155
+ }
156
+ const data = await finalizeMutationResult(raw, signer, "manage_tp_sl");
157
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
158
+ }
159
+ catch (error) {
160
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
161
+ }
162
+ },
163
+ };
@@ -35,7 +35,7 @@ function pickMarketDetail(res) {
35
35
  }
36
36
  export const openPositionSimpleTool = {
37
37
  name: "open_position_simple",
38
- description: "High-level open position helper. Computes size/price/tradingFee and submits an increase order. Human units by default; use 'raw:' prefix for raw units.",
38
+ description: "[TRADE] High-level open position helper. Computes size/price/tradingFee and submits an increase order. Human units by default; use 'raw:' prefix for raw units.",
39
39
  schema: {
40
40
  poolId: z.string().optional().describe("Hex Pool ID. Provide either poolId or keyword."),
41
41
  keyword: z.string().optional().describe('Recommended: Market keyword, e.g. "BTC", "ETH", "XRP".'),
@@ -56,7 +56,7 @@ export const openPositionSimpleTool = {
56
56
  slippagePct: z.coerce
57
57
  .string()
58
58
  .optional()
59
- .describe(`${SLIPPAGE_PCT_4DP_DESC} Default 100 (=1%).`),
59
+ .describe(`${SLIPPAGE_PCT_4DP_DESC} Default 50 (=0.5%).`),
60
60
  postOnly: z.coerce.boolean().optional().describe("Post-only (default false)."),
61
61
  executionFeeToken: z.string().optional().describe("Execution fee token address (default quoteToken)."),
62
62
  assetClass: z.coerce.number().int().nonnegative().optional().describe("Fee query assetClass (default 1)."),
@@ -65,6 +65,10 @@ export const openPositionSimpleTool = {
65
65
  .string()
66
66
  .optional()
67
67
  .describe("Trading fee. e.g. '0.2' USDC or 'raw:...'. Default: computed via getUserTradingFeeRate."),
68
+ tpPrice: z.coerce.string().optional().describe("Take Profit trigger price."),
69
+ tpSize: z.coerce.string().optional().describe("Take Profit size (base units). If omitted but tpPrice set, uses full position size."),
70
+ slPrice: z.coerce.string().optional().describe("Stop Loss trigger price."),
71
+ slSize: z.coerce.string().optional().describe("Stop Loss size (base units). If omitted but slPrice set, uses full position size."),
68
72
  autoApprove: z.coerce.boolean().optional().describe("If true, auto-approve token spend (default false)."),
69
73
  approveMax: z.coerce.boolean().optional().describe("If autoApprove, approve MaxUint256 (default false)."),
70
74
  autoDeposit: z.coerce
@@ -118,7 +122,7 @@ export const openPositionSimpleTool = {
118
122
  }
119
123
  const orderType = mapOrderType(args.orderType ?? 0);
120
124
  const postOnly = Boolean(args.postOnly ?? false);
121
- const slippagePct = normalizeSlippagePct4dp(args.slippagePct ?? "100");
125
+ const slippagePct = normalizeSlippagePct4dp(args.slippagePct ?? "50");
122
126
  const executionFeeToken = normalizeAddress(args.executionFeeToken || quoteToken, "executionFeeToken");
123
127
  // 4) Determine reference price (30 decimals)
124
128
  let price30;
@@ -222,6 +226,10 @@ export const openPositionSimpleTool = {
222
226
  autoDeposit: Boolean(args.autoDeposit ?? false),
223
227
  priceMeta,
224
228
  sizeMeta,
229
+ tpPrice: args.tpPrice ? parseUserPrice30(args.tpPrice, "tpPrice") : null,
230
+ tpSize: args.tpSize ? parseUserUnits(args.tpSize, baseDecimals, "tpSize") : (args.tpPrice ? sizeRaw : null),
231
+ slPrice: args.slPrice ? parseUserPrice30(args.slPrice, "slPrice") : null,
232
+ slSize: args.slSize ? parseUserUnits(args.slSize, baseDecimals, "slSize") : (args.slPrice ? sizeRaw : null),
225
233
  };
226
234
  if (args.dryRun) {
227
235
  return {
@@ -254,7 +262,7 @@ export const openPositionSimpleTool = {
254
262
  }
255
263
  }
256
264
  // 8) Submit increase order using existing trade service
257
- const raw = await openPosition(client, address, {
265
+ const openArgs = {
258
266
  poolId,
259
267
  positionId: "",
260
268
  orderType,
@@ -271,7 +279,16 @@ export const openPositionSimpleTool = {
271
279
  tradingFee: `raw:${String(tradingFeeRaw)}`,
272
280
  marketId,
273
281
  autoDeposit: Boolean(args.autoDeposit ?? false),
274
- });
282
+ };
283
+ if (prep.tpPrice) {
284
+ openArgs.tpPrice = `raw:${prep.tpPrice}`;
285
+ openArgs.tpSize = `raw:${prep.tpSize}`;
286
+ }
287
+ if (prep.slPrice) {
288
+ openArgs.slPrice = `raw:${prep.slPrice}`;
289
+ openArgs.slSize = `raw:${prep.slSize}`;
290
+ }
291
+ const raw = await openPosition(client, address, openArgs);
275
292
  const data = await finalizeMutationResult(raw, signer, "open_position_simple");
276
293
  const txHash = data.confirmation?.txHash;
277
294
  const verification = txHash ? await verifyTradeOutcome(client, address, poolId, txHash) : null;
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import * as allTools from "./index.js";
3
+ export const searchToolsTool = {
4
+ name: "search_tools",
5
+ description: "[UTILS] Search for available tools by keyword in their name or description.",
6
+ schema: {
7
+ keyword: z.string().describe("Keyword to search for in tool names or descriptions."),
8
+ },
9
+ handler: async (args) => {
10
+ try {
11
+ const keyword = args.keyword.toLowerCase();
12
+ const tools = Object.values(allTools);
13
+ const matches = tools
14
+ .filter(t => (t.name && t.name.toLowerCase().includes(keyword)) ||
15
+ (t.description && t.description.toLowerCase().includes(keyword)))
16
+ .map(t => ({
17
+ name: t.name,
18
+ description: t.description,
19
+ // We only return name and description to keep it concise
20
+ }));
21
+ if (matches.length === 0) {
22
+ return { content: [{ type: "text", text: `No tools found matching: ${args.keyword}` }] };
23
+ }
24
+ return {
25
+ content: [{
26
+ type: "text",
27
+ text: JSON.stringify({ status: "success", count: matches.length, tools: matches }, null, 2)
28
+ }]
29
+ };
30
+ }
31
+ catch (error) {
32
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
33
+ }
34
+ },
35
+ };
@@ -50,13 +50,16 @@ export const getHistoryOrderStatusDesc = (status) => {
50
50
  * MarketPoolState
51
51
  */
52
52
  export const getMarketStateDesc = (state) => {
53
- switch (state) {
54
- case 0: return "Created";
55
- case 1: return "WaitOracle";
56
- case 2: return "Active";
57
- case 3: return "PreDelisting";
58
- case 4: return "Delisted";
59
- default: return `Unknown(${state})`;
53
+ if (state === undefined || state === null || state === "")
54
+ return "Unknown";
55
+ const s = Number(state);
56
+ switch (s) {
57
+ case 0: return "Created (Pending Setup)";
58
+ case 1: return "WaitOracle (Waiting for Price)";
59
+ case 2: return "Active (Tradable)";
60
+ case 3: return "PreDelisting (Closing Only)";
61
+ case 4: return "Delisted (Closed)";
62
+ default: return `Other(${s})`;
60
63
  }
61
64
  };
62
65
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michaleffffff/mcp-trading-server",
3
- "version": "3.0.4",
3
+ "version": "3.0.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "myx-mcp": "dist/server.js"
@@ -1,57 +0,0 @@
1
- import { z } from "zod";
2
- import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
- import { finalizeMutationResult } from "../utils/mutationResult.js";
4
- function normalizeOrderIds(input) {
5
- if (Array.isArray(input)) {
6
- return input.map((id) => String(id).trim()).filter(Boolean);
7
- }
8
- if (typeof input === "string") {
9
- const text = input.trim();
10
- if (!text)
11
- return [];
12
- // Support toolchains that serialize arrays as JSON strings.
13
- if (text.startsWith("[") && text.endsWith("]")) {
14
- try {
15
- const parsed = JSON.parse(text);
16
- if (Array.isArray(parsed)) {
17
- return parsed.map((id) => String(id).trim()).filter(Boolean);
18
- }
19
- }
20
- catch {
21
- // Fallback to comma/single parsing below.
22
- }
23
- }
24
- if (text.includes(",")) {
25
- return text.split(",").map((id) => id.trim()).filter(Boolean);
26
- }
27
- return [text];
28
- }
29
- return [];
30
- }
31
- export const cancelAllOrdersTool = {
32
- name: "cancel_all_orders",
33
- description: "Cancel open orders by orderIds. Accepts array, JSON-array string, comma-separated string, or single orderId.",
34
- schema: {
35
- orderIds: z
36
- .union([z.array(z.string()).min(1), z.string().min(1)])
37
- .describe("Order IDs to cancel. Supports array or string (single/comma/JSON-array)."),
38
- },
39
- handler: async (args) => {
40
- try {
41
- const { client, signer } = await resolveClient();
42
- const chainId = getChainId();
43
- const orderIds = normalizeOrderIds(args.orderIds);
44
- if (orderIds.length === 0) {
45
- throw new Error("orderIds is required and must include at least one non-empty order id.");
46
- }
47
- const raw = await client.order.cancelAllOrders(orderIds, chainId);
48
- const result = await finalizeMutationResult(raw, signer, "cancel_all_orders");
49
- return {
50
- content: [{ type: "text", text: JSON.stringify({ status: "success", data: { cancelled: orderIds.length, orderIds, result } }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }],
51
- };
52
- }
53
- catch (error) {
54
- return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
55
- }
56
- },
57
- };
@@ -1,22 +0,0 @@
1
- import { z } from "zod";
2
- import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
- import { finalizeMutationResult } from "../utils/mutationResult.js";
4
- export const cancelOrderTool = {
5
- name: "cancel_order",
6
- description: "Cancel an open order by its order ID.",
7
- schema: {
8
- orderId: z.string().describe("Order ID to cancel"),
9
- },
10
- handler: async (args) => {
11
- try {
12
- const { client, signer } = await resolveClient();
13
- const chainId = getChainId();
14
- const raw = await client.order.cancelOrder(args.orderId, chainId);
15
- const data = await finalizeMutationResult(raw, signer, "cancel_order");
16
- return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
17
- }
18
- catch (error) {
19
- return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
20
- }
21
- },
22
- };
@@ -1,20 +0,0 @@
1
- import { z } from "zod";
2
- import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
- export const getAccountVipInfoTool = {
4
- name: "get_account_vip_info",
5
- description: "Get account VIP/fee-tier information.",
6
- schema: {
7
- chainId: z.number().int().positive().optional().describe("Optional chainId override"),
8
- },
9
- handler: async (args) => {
10
- try {
11
- const { client, address } = await resolveClient();
12
- const chainId = args.chainId ?? getChainId();
13
- const result = await client.account.getAccountVipInfo(chainId, address);
14
- return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
15
- }
16
- catch (error) {
17
- return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
18
- }
19
- },
20
- };
@@ -1,28 +0,0 @@
1
- import { z } from "zod";
2
- import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
- const klineIntervalSchema = z.enum(["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "1M"]);
4
- export const getKlineLatestBarTool = {
5
- name: "get_kline_latest_bar",
6
- description: "Get the latest single K-line/candlestick bar for a pool.",
7
- schema: {
8
- poolId: z.string().describe("Pool ID"),
9
- interval: klineIntervalSchema.describe("K-line interval"),
10
- },
11
- handler: async (args) => {
12
- try {
13
- const { client } = await resolveClient();
14
- const chainId = getChainId();
15
- const result = await client.markets.getKlineLatestBar({
16
- poolId: args.poolId,
17
- chainId,
18
- interval: args.interval,
19
- limit: 1,
20
- endTime: Date.now(),
21
- });
22
- return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
23
- }
24
- catch (error) {
25
- return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
26
- }
27
- },
28
- };
@@ -1,22 +0,0 @@
1
- import { z } from "zod";
2
- import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
- import { getOraclePrice } from "../services/marketService.js";
4
- export const getOraclePriceTool = {
5
- name: "get_oracle_price",
6
- description: "Get the current oracle price for a specific pool.",
7
- schema: {
8
- poolId: z.string().describe("Pool ID to get oracle price for"),
9
- chainId: z.number().int().positive().optional().describe("Optional chainId override"),
10
- },
11
- handler: async (args) => {
12
- try {
13
- const { client } = await resolveClient();
14
- const chainId = args.chainId ?? getChainId();
15
- const data = await getOraclePrice(client, args.poolId, chainId);
16
- return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
17
- }
18
- catch (error) {
19
- return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
20
- }
21
- },
22
- };
@@ -1,17 +0,0 @@
1
- import { resolveClient } from "../auth/resolveClient.js";
2
- import { getPoolList } from "../services/marketService.js";
3
- export const getPoolListTool = {
4
- name: "get_pool_list",
5
- description: "Get the complete list of all available tradable pools directly from the SDK.",
6
- schema: {},
7
- handler: async () => {
8
- try {
9
- const { client } = await resolveClient();
10
- const result = await getPoolList(client);
11
- return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
12
- }
13
- catch (error) {
14
- return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
15
- }
16
- },
17
- };
@@ -1,16 +0,0 @@
1
- import { resolveClient } from "../auth/resolveClient.js";
2
- export const getPoolSymbolAllTool = {
3
- name: "get_pool_symbol_all",
4
- description: "Get symbol/icon metadata for all pools.",
5
- schema: {},
6
- handler: async () => {
7
- try {
8
- const { client } = await resolveClient();
9
- const result = await client.markets.getPoolSymbolAll();
10
- return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
11
- }
12
- catch (error) {
13
- return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
14
- }
15
- },
16
- };