@michaleffffff/mcp-trading-server 2.3.6 → 2.4.1

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/dist/server.js CHANGED
@@ -81,7 +81,7 @@ function zodSchemaToJsonSchema(zodSchema) {
81
81
  };
82
82
  }
83
83
  // ─── MCP Server ───
84
- const server = new Server({ name: "myx-mcp-trading-server", version: "2.3.6" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
84
+ const server = new Server({ name: "myx-mcp-trading-server", version: "2.4.1" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
85
85
  // List tools
86
86
  server.setRequestHandler(ListToolsRequestSchema, async () => {
87
87
  return {
@@ -105,7 +105,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
105
105
  try {
106
106
  let validatedArgs = args ?? {};
107
107
  if (tool.schema) {
108
- validatedArgs = z.object(tool.schema).parse(args ?? {});
108
+ validatedArgs = z.object(tool.schema).strict().parse(args ?? {});
109
109
  }
110
110
  logger.toolExecution(name, validatedArgs);
111
111
  const result = await tool.handler(validatedArgs);
@@ -181,7 +181,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
181
181
  async function main() {
182
182
  const transport = new StdioServerTransport();
183
183
  await server.connect(transport);
184
- logger.info("🚀 MYX Trading MCP Server v2.3.6 running (stdio, pure on-chain, prod ready)");
184
+ logger.info("🚀 MYX Trading MCP Server v2.4.1 running (stdio, pure on-chain, prod ready)");
185
185
  }
186
186
  main().catch((err) => {
187
187
  logger.error("Fatal Server Startup Error", err);
@@ -1,20 +1,20 @@
1
1
  import { getChainId } from "../auth/resolveClient.js";
2
2
  import { getMarketStateDesc } from "../utils/mappings.js";
3
- export async function getMarketPrice(client, poolId) {
4
- const chainId = getChainId();
3
+ export async function getMarketPrice(client, poolId, chainIdOverride) {
4
+ const chainId = chainIdOverride ?? getChainId();
5
5
  const ticker = await client.markets.getTickerList({
6
6
  chainId,
7
7
  poolIds: [poolId],
8
8
  });
9
9
  return ticker?.data?.[0] ?? null;
10
10
  }
11
- export async function getOraclePrice(client, poolId) {
12
- const chainId = getChainId();
11
+ export async function getOraclePrice(client, poolId, chainIdOverride) {
12
+ const chainId = chainIdOverride ?? getChainId();
13
13
  return client.utils.getOraclePrice(poolId, chainId);
14
14
  }
15
15
  export async function searchMarket(client, keyword, limit = 1000) {
16
16
  const chainId = getChainId();
17
- const searchRes = await client.markets.searchMarket({ chainId, searchKey: keyword, limit });
17
+ const searchRes = await client.markets.searchMarket({ chainId, keyword, limit });
18
18
  const raw = searchRes;
19
19
  let dataList = [];
20
20
  if (raw && raw.contractInfo && Array.isArray(raw.contractInfo.list)) {
@@ -44,7 +44,7 @@ export async function searchMarket(client, keyword, limit = 1000) {
44
44
  }
45
45
  }
46
46
  return activeMarkets.map(m => {
47
- const ticker = tickers.find(t => t.poolId.toLowerCase() === m.poolId.toLowerCase());
47
+ const ticker = tickers.find(t => String(t.poolId).toLowerCase() === String(m.poolId).toLowerCase());
48
48
  return {
49
49
  symbol: m.baseQuoteSymbol || `${m.baseSymbol}/${m.quoteSymbol}`,
50
50
  name: m.symbolName || m.name,
@@ -57,8 +57,8 @@ export async function searchMarket(client, keyword, limit = 1000) {
57
57
  };
58
58
  });
59
59
  }
60
- export async function getMarketDetail(client, poolId) {
61
- const chainId = getChainId();
60
+ export async function getMarketDetail(client, poolId, chainIdOverride) {
61
+ const chainId = chainIdOverride ?? getChainId();
62
62
  const res = await client.markets.getMarketDetail({ chainId, poolId });
63
63
  // Ensure it's returned as { data: ... } for consistency with other services if needed,
64
64
  // but looking at existing tools, they often stringify the whole result.
@@ -74,7 +74,7 @@ export async function getPoolList(client) {
74
74
  /**
75
75
  * 获取池子分级配置
76
76
  */
77
- export async function getPoolLevelConfig(client, poolId) {
78
- const chainId = getChainId();
79
- return client.api.getPoolLevelConfig({ poolId, chainId });
77
+ export async function getPoolLevelConfig(client, poolId, chainIdOverride) {
78
+ const chainId = chainIdOverride ?? getChainId();
79
+ return client.markets.getPoolLevelConfig(poolId, chainId);
80
80
  }
@@ -1,6 +1,5 @@
1
1
  import { pool, quote, base } from "@myx-trade/sdk";
2
2
  import { getChainId, resolveClient } from "../auth/resolveClient.js";
3
- import { ensureUnits } from "../utils/units.js";
4
3
  /**
5
4
  * 创建合约市场池子
6
5
  */
@@ -12,58 +11,57 @@ export async function createPool(baseToken, marketId) {
12
11
  /**
13
12
  * 获取池子信息
14
13
  */
15
- export async function getPoolInfo(poolId) {
16
- const chainId = getChainId();
14
+ export async function getPoolInfo(poolId, chainIdOverride) {
15
+ const chainId = chainIdOverride ?? getChainId();
17
16
  return pool.getPoolInfo(chainId, poolId);
18
17
  }
19
18
  /**
20
19
  * 获取池子详情
21
20
  */
22
- export async function getPoolDetail(poolId) {
23
- const chainId = getChainId();
21
+ export async function getPoolDetail(poolId, chainIdOverride) {
22
+ const chainId = chainIdOverride ?? getChainId();
24
23
  return pool.getPoolDetail(chainId, poolId);
25
24
  }
26
25
  /**
27
26
  * 获取流动性信息
28
27
  */
29
- export async function getLiquidityInfo(client, poolId, marketPrice) {
30
- const chainId = getChainId();
31
- const price30 = ensureUnits(marketPrice, 30, "marketPrice");
32
- return client.utils.getLiquidityInfo({ chainId, poolId, marketPrice: price30 });
28
+ export async function getLiquidityInfo(client, poolId, marketPrice, chainIdOverride) {
29
+ const chainId = chainIdOverride ?? getChainId();
30
+ return client.utils.getLiquidityInfo({ chainId, poolId, marketPrice });
33
31
  }
34
32
  /**
35
33
  * Quote 池 deposit
36
34
  */
37
- export async function quoteDeposit(poolId, amount, slippage) {
38
- const chainId = getChainId();
35
+ export async function quoteDeposit(poolId, amount, slippage, chainIdOverride) {
36
+ const chainId = chainIdOverride ?? getChainId();
39
37
  return quote.deposit({ chainId, poolId, amount, slippage });
40
38
  }
41
39
  /**
42
40
  * Quote 池 withdraw
43
41
  */
44
- export async function quoteWithdraw(poolId, amount, slippage) {
45
- const chainId = getChainId();
42
+ export async function quoteWithdraw(poolId, amount, slippage, chainIdOverride) {
43
+ const chainId = chainIdOverride ?? getChainId();
46
44
  return quote.withdraw({ chainId, poolId, amount, slippage });
47
45
  }
48
46
  /**
49
47
  * Base 池 deposit
50
48
  */
51
- export async function baseDeposit(poolId, amount, slippage) {
52
- const chainId = getChainId();
49
+ export async function baseDeposit(poolId, amount, slippage, chainIdOverride) {
50
+ const chainId = chainIdOverride ?? getChainId();
53
51
  return base.deposit({ chainId, poolId, amount, slippage });
54
52
  }
55
53
  /**
56
54
  * Base 池 withdraw
57
55
  */
58
- export async function baseWithdraw(poolId, amount, slippage) {
59
- const chainId = getChainId();
56
+ export async function baseWithdraw(poolId, amount, slippage, chainIdOverride) {
57
+ const chainId = chainIdOverride ?? getChainId();
60
58
  return base.withdraw({ chainId, poolId, amount, slippage });
61
59
  }
62
60
  /**
63
61
  * 获取 LP 价格
64
62
  */
65
- export async function getLpPrice(poolType, poolId) {
66
- const chainId = getChainId();
63
+ export async function getLpPrice(poolType, poolId, chainIdOverride) {
64
+ const chainId = chainIdOverride ?? getChainId();
67
65
  if (poolType === "BASE") {
68
66
  return base.getLpPrice(chainId, poolId);
69
67
  }
@@ -1,7 +1,7 @@
1
1
  import { Direction, OrderType, TriggerType } from "@myx-trade/sdk";
2
2
  import { parseUnits } from "ethers";
3
- import { getChainId, getQuoteToken, getQuoteDecimals } from "../auth/resolveClient.js";
4
- import { ensureUnits, parseHumanUnits } from "../utils/units.js";
3
+ import { getChainId, getQuoteToken } from "../auth/resolveClient.js";
4
+ import { ensureUnits } from "../utils/units.js";
5
5
  import { normalizeAddress } from "../utils/address.js";
6
6
  /**
7
7
  * 将人类可读数值转为指定精度的整数字串
@@ -20,114 +20,61 @@ function resolveDirection(direction) {
20
20
  * 开仓 / 加仓
21
21
  */
22
22
  export async function openPosition(client, address, args) {
23
- const maxTradeAmount = process.env.MAX_TRADE_AMOUNT;
24
- if (maxTradeAmount && Number(args.collateralAmount) > Number(maxTradeAmount)) {
25
- throw new Error(`Security Exception: collateralAmount (${args.collateralAmount}) exceeds MAX_TRADE_AMOUNT (${maxTradeAmount}). Update .env if you need to trade larger sizes.`);
26
- }
27
23
  const chainId = getChainId();
28
- const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
29
- const quoteDecimals = args.quoteDecimals ?? getQuoteDecimals();
30
- const rawLeverage = args.leverage ?? 10;
31
- if (!Number.isFinite(rawLeverage) || rawLeverage <= 0) {
32
- throw new Error("leverage must be a positive number.");
33
- }
34
- const leverage = Math.floor(rawLeverage);
35
- let calcPrice30;
36
- let orderPrice30;
37
- if (args.price) {
38
- const limitPrice30 = parseHumanUnits(args.price, 30, "price");
39
- calcPrice30 = limitPrice30;
40
- orderPrice30 = limitPrice30;
41
- }
42
- else {
43
- const oracleData = await client.utils.getOraclePrice(args.poolId, chainId);
44
- calcPrice30 = ensureUnits(oracleData.price, 30, "oracle price");
45
- orderPrice30 = calcPrice30;
46
- console.error(`[Market Order] Fetched oracle price for size math: ${calcPrice30}`);
47
- }
48
- // 保证金转为 quote token 精度
49
- const collateralWei = parseHumanUnits(args.collateralAmount, quoteDecimals, "collateralAmount");
50
- // 仓位大小 (size): 在 MYX 这一版中,size 表示 Base Token(WETH)的数量,精度通常为 18
51
- // 计算公式:size = (collateral * leverage) / price
52
- // 为了防止浮点溢出,我们可以全部转为 BigInt 计算
53
- const collateralBig = BigInt(collateralWei);
54
- const leverageBig = BigInt(leverage);
55
- const priceBig = BigInt(calcPrice30);
56
- let size = "0";
57
- if (priceBig > 0n) {
58
- // collateral: 6位, price: 30位, 目标size: 18位
59
- // (collateral * leverage * 10^(18 + 30 - 6)) / price
60
- const exponent = BigInt(18 + 30 - quoteDecimals);
61
- size = ((collateralBig * leverageBig * (10n ** exponent)) / priceBig).toString();
62
- }
63
24
  const dir = resolveDirection(args.direction);
64
- // 根据方向决定 triggerType: 做多分级(如果现价>限价则是低买,需等跌,用 LTE;反之亦然,这里简化为通用 LTE(多)/GTE(空))
65
- const triggerType = args.price
66
- ? (args.direction === 0 ? TriggerType.LTE : TriggerType.GTE)
67
- : TriggerType.NONE; // 没传价就是市价单
25
+ const executionFeeToken = normalizeAddress(args.executionFeeToken, "executionFeeToken");
68
26
  const orderParams = {
69
27
  chainId,
70
28
  address,
71
29
  poolId: args.poolId,
72
- // 对于新开仓,传空字符串 ""
73
- positionId: args.positionId || "",
74
- orderType: args.price ? OrderType.LIMIT : OrderType.MARKET,
75
- triggerType: triggerType,
30
+ positionId: args.positionId,
31
+ orderType: args.orderType,
32
+ triggerType: args.triggerType,
76
33
  direction: dir,
77
- collateralAmount: collateralWei,
78
- size,
79
- price: orderPrice30,
80
- timeInForce: 0,
81
- postOnly: false,
82
- slippagePct: args.slippagePct ?? "100", // BPS
83
- executionFeeToken: quoteToken,
84
- leverage,
34
+ collateralAmount: args.collateralAmount,
35
+ size: args.size,
36
+ price: args.price,
37
+ timeInForce: args.timeInForce,
38
+ postOnly: args.postOnly,
39
+ slippagePct: args.slippagePct,
40
+ executionFeeToken,
41
+ leverage: args.leverage,
85
42
  };
86
- // 可选止盈止损
87
- if (args.tpPrice) {
88
- orderParams.tpPrice = parseHumanUnits(args.tpPrice, 30, "tpPrice");
89
- orderParams.tpSize = size;
90
- }
91
- if (args.slPrice) {
92
- orderParams.slPrice = parseHumanUnits(args.slPrice, 30, "slPrice");
93
- orderParams.slSize = size;
94
- }
95
- // tradingFee: SDK 第二个参数
96
- const tradingFeeStr = (args.tradingFee || "0").toString();
97
- // 获取 marketId,SDK 0.1.267 要求 as 3rd parameter
98
- const marketDetailRes = await client.markets.getMarketDetail({ chainId, poolId: args.poolId });
99
- const marketId = marketDetailRes?.marketId || marketDetailRes?.data?.marketId;
100
- if (!marketId) {
101
- throw new Error(`Could not find marketId for poolId: ${args.poolId}`);
102
- }
103
- return client.order.createIncreaseOrder(orderParams, tradingFeeStr, marketId);
43
+ if (args.tpSize)
44
+ orderParams.tpSize = args.tpSize;
45
+ if (args.tpPrice)
46
+ orderParams.tpPrice = args.tpPrice;
47
+ if (args.slSize)
48
+ orderParams.slSize = args.slSize;
49
+ if (args.slPrice)
50
+ orderParams.slPrice = args.slPrice;
51
+ return client.order.createIncreaseOrder(orderParams, args.tradingFee, args.marketId);
104
52
  }
105
53
  /**
106
54
  * 平仓 / 减仓
107
55
  */
108
56
  export async function closePosition(client, address, args) {
109
57
  const chainId = getChainId();
110
- const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
58
+ const executionFeeToken = normalizeAddress(args.executionFeeToken, "executionFeeToken");
111
59
  if (args.direction === undefined || args.direction === null) {
112
60
  throw new Error("direction is required (0=LONG, 1=SHORT), must match position direction.");
113
61
  }
114
- const price30 = parseHumanUnits(args.price, 30, "price");
115
- const sizeWei = ensureUnits(args.size, 18, "size");
116
62
  const dir = resolveDirection(args.direction);
117
63
  return client.order.createDecreaseOrder({
118
64
  chainId,
119
65
  address,
120
66
  poolId: args.poolId,
121
67
  positionId: args.positionId,
122
- orderType: OrderType.MARKET,
123
- triggerType: TriggerType.NONE,
68
+ orderType: args.orderType,
69
+ triggerType: args.triggerType,
124
70
  direction: dir,
125
- collateralAmount: "0",
126
- size: sizeWei,
127
- price: price30,
128
- postOnly: false,
129
- slippagePct: args.slippagePct ?? "100",
130
- executionFeeToken: quoteToken,
71
+ collateralAmount: args.collateralAmount,
72
+ size: args.size,
73
+ price: args.price,
74
+ timeInForce: args.timeInForce,
75
+ postOnly: args.postOnly,
76
+ slippagePct: args.slippagePct,
77
+ executionFeeToken,
131
78
  leverage: args.leverage,
132
79
  });
133
80
  }
@@ -136,7 +83,7 @@ export async function closePosition(client, address, args) {
136
83
  */
137
84
  export async function setPositionTpSl(client, address, args) {
138
85
  const chainId = getChainId();
139
- const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
86
+ const executionFeeToken = normalizeAddress(args.executionFeeToken, "executionFeeToken");
140
87
  if (!args.tpPrice && !args.slPrice) {
141
88
  throw new Error("At least one of tpPrice or slPrice must be provided.");
142
89
  }
@@ -148,19 +95,19 @@ export async function setPositionTpSl(client, address, args) {
148
95
  positionId: args.positionId,
149
96
  direction: dir,
150
97
  leverage: args.leverage,
151
- executionFeeToken: quoteToken,
152
- tpTriggerType: args.direction === 0 ? TriggerType.GTE : TriggerType.LTE,
153
- slTriggerType: args.direction === 0 ? TriggerType.LTE : TriggerType.GTE,
154
- slippagePct: "100", // 默认 100 BPS = 1%
98
+ executionFeeToken,
99
+ tpTriggerType: args.tpTriggerType,
100
+ slTriggerType: args.slTriggerType,
101
+ slippagePct: args.slippagePct,
155
102
  };
156
- if (args.tpPrice) {
157
- params.tpPrice = parseHumanUnits(args.tpPrice, 30, "tpPrice");
158
- params.tpSize = args.tpSize || "0";
159
- }
160
- if (args.slPrice) {
161
- params.slPrice = parseHumanUnits(args.slPrice, 30, "slPrice");
162
- params.slSize = args.slSize || "0";
163
- }
103
+ if (args.tpPrice)
104
+ params.tpPrice = args.tpPrice;
105
+ if (args.tpSize)
106
+ params.tpSize = args.tpSize;
107
+ if (args.slPrice)
108
+ params.slPrice = args.slPrice;
109
+ if (args.slSize)
110
+ params.slSize = args.slSize;
164
111
  return client.order.createPositionTpSlOrder(params);
165
112
  }
166
113
  /**
@@ -169,11 +116,14 @@ export async function setPositionTpSl(client, address, args) {
169
116
  export async function adjustMargin(client, address, args) {
170
117
  const chainId = getChainId();
171
118
  const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
172
- const quoteDecimals = args.quoteDecimals ?? getQuoteDecimals();
119
+ const adjustAmount = String(args.adjustAmount ?? "").trim();
120
+ if (!/^-?\d+$/.test(adjustAmount)) {
121
+ throw new Error("adjustAmount must be an integer string in quote token raw units.");
122
+ }
173
123
  return client.position.adjustCollateral({
174
124
  poolId: args.poolId,
175
125
  positionId: args.positionId,
176
- adjustAmount: parseHumanUnits(args.adjustAmount, quoteDecimals, "adjustAmount"),
126
+ adjustAmount,
177
127
  quoteToken,
178
128
  chainId,
179
129
  address,
@@ -231,17 +181,17 @@ export async function closeAllPositions(client, address) {
231
181
  */
232
182
  export async function updateOrderTpSl(client, address, args) {
233
183
  const chainId = getChainId();
234
- const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
184
+ const quoteToken = normalizeAddress(args.quoteToken, "quoteToken");
235
185
  const params = {
236
186
  orderId: args.orderId,
237
- tpSize: args.tpSize || "0",
238
- tpPrice: args.tpPrice ? parseHumanUnits(args.tpPrice, 30, "tpPrice") : "0",
239
- slSize: args.slSize || "0",
240
- slPrice: args.slPrice ? parseHumanUnits(args.slPrice, 30, "slPrice") : "0",
241
- useOrderCollateral: args.useOrderCollateral ?? true,
187
+ tpSize: args.tpSize,
188
+ tpPrice: args.tpPrice,
189
+ slSize: args.slSize,
190
+ slPrice: args.slPrice,
191
+ useOrderCollateral: args.useOrderCollateral,
242
192
  executionFeeToken: quoteToken,
243
- size: args.size || "0",
244
- price: args.price || "0",
193
+ size: args.size,
194
+ price: args.price,
245
195
  };
246
- return client.order.updateOrderTpSl(params, quoteToken, chainId, address, args.marketId, args.isTpSlOrder ?? true);
196
+ return client.order.updateOrderTpSl(params, quoteToken, chainId, address, args.marketId, args.isTpSlOrder);
247
197
  }
@@ -40,14 +40,14 @@ export const getTradeFlowTool = {
40
40
  name: "get_trade_flow",
41
41
  description: "Get account trade flow / transaction history.",
42
42
  schema: {
43
- page: z.coerce.number().optional().describe("Page number (default 1)"),
44
- limit: z.coerce.number().optional().describe("Results per page (default 20)"),
43
+ poolId: z.string().optional().describe("Optional pool ID filter."),
44
+ limit: z.number().int().positive().optional().describe("Results per page (default 20)"),
45
45
  },
46
46
  handler: async (args) => {
47
47
  try {
48
48
  const { client, address } = await resolveClient();
49
49
  const chainId = getChainId();
50
- const query = { chainId, page: args.page ?? 1, limit: args.limit ?? 20 };
50
+ const query = { chainId, poolId: args.poolId, limit: args.limit ?? 20 };
51
51
  const result = await client.account.getTradeFlow(query, address);
52
52
  const enhancedData = (result?.data || []).map((flow) => ({
53
53
  ...flow,
@@ -1,19 +1,18 @@
1
1
  import { z } from "zod";
2
- import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  import { normalizeAddress } from "../utils/address.js";
4
- import { booleanLike } from "../utils/schema.js";
5
4
  export const accountDepositTool = {
6
5
  name: "account_deposit",
7
6
  description: "Deposit funds from wallet into the MYX trading account.",
8
7
  schema: {
9
- amount: z.string().describe("Amount to deposit (in token decimals)"),
10
- tokenAddress: z.string().optional().describe("Token address (default: quote token)"),
8
+ amount: z.string().regex(/^\d+$/).describe("Amount to deposit (token raw units)"),
9
+ tokenAddress: z.string().describe("Token address"),
11
10
  },
12
11
  handler: async (args) => {
13
12
  try {
14
13
  const { client } = await resolveClient();
15
14
  const chainId = getChainId();
16
- const tokenAddress = normalizeAddress(args.tokenAddress || getQuoteToken(), "tokenAddress");
15
+ const tokenAddress = normalizeAddress(args.tokenAddress, "tokenAddress");
17
16
  const result = await client.account.deposit({
18
17
  amount: args.amount,
19
18
  tokenAddress,
@@ -31,8 +30,8 @@ export const accountWithdrawTool = {
31
30
  description: "Withdraw funds from MYX trading account back to wallet.",
32
31
  schema: {
33
32
  poolId: z.string().describe("Pool ID to withdraw from"),
34
- amount: z.string().describe("Amount to withdraw (in token decimals)"),
35
- isQuoteToken: booleanLike.optional().describe("Whether to withdraw as quote token (default true)"),
33
+ amount: z.string().regex(/^\d+$/).describe("Amount to withdraw (token raw units)"),
34
+ isQuoteToken: z.boolean().describe("Whether to withdraw as quote token"),
36
35
  },
37
36
  handler: async (args) => {
38
37
  try {
@@ -43,7 +42,7 @@ export const accountWithdrawTool = {
43
42
  receiver: address,
44
43
  amount: args.amount,
45
44
  poolId: args.poolId,
46
- isQuoteToken: args.isQuoteToken !== false,
45
+ isQuoteToken: args.isQuoteToken,
47
46
  });
48
47
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
49
48
  }
@@ -7,7 +7,7 @@ export const adjustMarginTool = {
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID"),
9
9
  positionId: z.string().describe("Position ID"),
10
- adjustAmount: z.string().describe("Amount in token decimals. Positive = add, negative = remove."),
10
+ adjustAmount: z.string().regex(/^-?\d+$/).describe("Quote token raw units. Positive = add, negative = remove."),
11
11
  quoteToken: z.string().optional().describe("Quote token address"),
12
12
  },
13
13
  handler: async (args) => {
@@ -2,28 +2,15 @@ import { z } from "zod";
2
2
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  export const cancelAllOrdersTool = {
4
4
  name: "cancel_all_orders",
5
- description: "Cancel all open orders, or cancel the provided orderIds in one call.",
5
+ description: "Cancel multiple open orders by orderIds.",
6
6
  schema: {
7
- orderIds: z.array(z.string()).optional().describe("Optional explicit order IDs. If omitted, all open orders will be cancelled."),
7
+ orderIds: z.array(z.string()).min(1).describe("Order IDs to cancel."),
8
8
  },
9
9
  handler: async (args) => {
10
10
  try {
11
- const { client, address } = await resolveClient();
11
+ const { client } = await resolveClient();
12
12
  const chainId = getChainId();
13
- let orderIds = Array.isArray(args.orderIds) ? args.orderIds : [];
14
- if (orderIds.length === 0) {
15
- const openOrders = await client.order.getOrders(address);
16
- const rows = Array.isArray(openOrders?.data) ? openOrders.data : [];
17
- orderIds = rows
18
- .map((o) => o.orderId ?? o.id ?? o.order_id)
19
- .filter((id) => id !== undefined && id !== null)
20
- .map((id) => String(id));
21
- }
22
- if (orderIds.length === 0) {
23
- return {
24
- content: [{ type: "text", text: JSON.stringify({ status: "success", data: { message: "No open orders to cancel.", cancelled: 0 } }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }],
25
- };
26
- }
13
+ const orderIds = args.orderIds;
27
14
  const result = await client.order.cancelAllOrders(orderIds, chainId);
28
15
  return {
29
16
  content: [{ type: "text", text: JSON.stringify({ status: "success", data: { cancelled: orderIds.length, orderIds, result } }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }],
@@ -4,22 +4,13 @@ export const cancelOrderTool = {
4
4
  name: "cancel_order",
5
5
  description: "Cancel an open order by its order ID.",
6
6
  schema: {
7
- orderIds: z.array(z.string()).describe("Array of order IDs to cancel"),
7
+ orderId: z.string().describe("Order ID to cancel"),
8
8
  },
9
9
  handler: async (args) => {
10
10
  try {
11
11
  const { client } = await resolveClient();
12
12
  const chainId = getChainId();
13
- const ids = args.orderIds;
14
- if (!ids || ids.length === 0)
15
- throw new Error("orderIds array is required.");
16
- let result;
17
- if (ids.length === 1) {
18
- result = await client.order.cancelOrder(ids[0], chainId);
19
- }
20
- else {
21
- result = await client.order.cancelOrders(ids, chainId);
22
- }
13
+ const result = await client.order.cancelOrder(args.orderId, chainId);
23
14
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
24
15
  }
25
16
  catch (error) {
@@ -1,16 +1,15 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
3
3
  import { normalizeAddress } from "../utils/address.js";
4
- import { booleanLike } from "../utils/schema.js";
5
4
  const MAX_UINT256 = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
6
5
  export const checkApprovalTool = {
7
6
  name: "check_approval",
8
7
  description: "Check if token spending approval is needed. Supports auto-approve exact amount (default) or optional unlimited approval.",
9
8
  schema: {
10
- amount: z.string().describe("Amount to check approval for (in token decimals)"),
9
+ amount: z.string().regex(/^\d+$/).describe("Amount to check approval for (token raw units)"),
11
10
  quoteToken: z.string().optional().describe("Token address to check. Uses default if omitted."),
12
- autoApprove: booleanLike.optional().describe("If true, automatically approve when needed."),
13
- approveMax: booleanLike.optional().describe("If true with autoApprove, approve unlimited MaxUint256 (default false: approve exact amount only)."),
11
+ autoApprove: z.boolean().optional().describe("If true, automatically approve when needed."),
12
+ approveMax: z.boolean().optional().describe("If true with autoApprove, approve unlimited MaxUint256 (default false: approve exact amount only)."),
14
13
  },
15
14
  handler: async (args) => {
16
15
  try {
@@ -7,7 +7,7 @@ export const closeAllPositionsTool = {
7
7
  description: "Emergency: close ALL open positions in a pool at once. Use for risk management.",
8
8
  schema: {
9
9
  poolId: z.string().describe("Pool ID to close all positions in"),
10
- slippagePct: z.string().optional().describe("Slippage in bps (default '200' = 2% for emergency)"),
10
+ slippagePct: z.string().regex(/^\d+$/).optional().describe("Slippage in 4-digit precision raw units (1 = 0.01%)"),
11
11
  },
12
12
  handler: async (args) => {
13
13
  try {
@@ -60,7 +60,7 @@ export const closeAllPositionsTool = {
60
60
  postOnly: false,
61
61
  slippagePct,
62
62
  executionFeeToken: pos.quoteToken || pos.quote_token || getQuoteToken(),
63
- leverage: pos.leverage || 1,
63
+ leverage: pos.userLeverage ?? pos.leverage ?? 1,
64
64
  };
65
65
  });
66
66
  const result = await client.order.closeAllPositions(chainId, closeParams);
@@ -3,18 +3,21 @@ import { resolveClient } from "../auth/resolveClient.js";
3
3
  import { closePosition as closePos } from "../services/tradeService.js";
4
4
  export const closePositionTool = {
5
5
  name: "close_position",
6
- description: "Close an open position.",
6
+ description: "Create a decrease order using SDK-native parameters.",
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID"),
9
9
  positionId: z.string().describe("Position ID to close"),
10
- direction: z.coerce.number().int().refine((value) => value === 0 || value === 1, {
11
- message: "direction must be 0 (LONG) or 1 (SHORT)",
12
- }).describe("0 = LONG, 1 = SHORT (must match position direction)"),
13
- leverage: z.coerce.number().describe("Position leverage"),
14
- size: z.string().describe("Size to close (human-readable or 18-decimal raw units)"),
15
- price: z.string().describe("Price in human-readable units"),
16
- slippagePct: z.string().optional().describe("Slippage in bps, default '100'"),
17
- quoteToken: z.string().optional().describe("Quote token address"),
10
+ orderType: z.number().int().min(0).max(3).describe("OrderType enum value"),
11
+ triggerType: z.number().int().min(0).max(2).describe("TriggerType enum value"),
12
+ direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT"),
13
+ collateralAmount: z.string().regex(/^\d+$/).describe("Collateral raw units"),
14
+ size: z.string().regex(/^\d+$/).describe("Position size raw units"),
15
+ price: z.string().regex(/^\d+$/).describe("Price raw units (30 decimals)"),
16
+ timeInForce: z.number().int().describe("TimeInForce enum value"),
17
+ postOnly: z.boolean().describe("Post-only flag"),
18
+ slippagePct: z.string().regex(/^\d+$/).describe("Slippage with 4-dec precision raw units"),
19
+ executionFeeToken: z.string().describe("Execution fee token address"),
20
+ leverage: z.number().describe("Leverage"),
18
21
  },
19
22
  handler: async (args) => {
20
23
  try {
@@ -3,21 +3,27 @@ import { resolveClient } from "../auth/resolveClient.js";
3
3
  import { openPosition } from "../services/tradeService.js";
4
4
  export const executeTradeTool = {
5
5
  name: "execute_trade",
6
- description: "Execute a new trade or add to an existing position.",
6
+ description: "Create an increase order using SDK-native parameters.",
7
7
  schema: {
8
- poolId: z.string().describe("Pool ID to trade in"),
9
- direction: z.coerce.number().int().refine((value) => value === 0 || value === 1, {
10
- message: "direction must be 0 (LONG) or 1 (SHORT)",
11
- }).describe("0 = LONG, 1 = SHORT"),
12
- collateralAmount: z.string().describe("Collateral in human-readable units (e.g. '100' for 100 USDC)"),
13
- leverage: z.coerce.number().optional().describe("Leverage multiplier (e.g. 10, default 10)"),
14
- price: z.string().optional().describe("Limit price in human-readable units (e.g. '3000'). Required for LIMIT orders, omitted for MARKET orders."),
15
- quoteToken: z.string().optional().describe("Quote token address. Uses env default if omitted."),
16
- quoteDecimals: z.coerce.number().optional().describe("Quote token decimals (default 6)."),
17
- slippagePct: z.string().optional().describe("Slippage in bps, default '100' = 1%"),
18
- positionId: z.string().optional().describe("Existing position ID to add to. Default '0' = new position."),
19
- tpPrice: z.string().optional().describe("Take-profit price (human-readable)"),
20
- slPrice: z.string().optional().describe("Stop-loss price (human-readable)"),
8
+ poolId: z.string().describe("Pool ID"),
9
+ positionId: z.string().describe("Position ID ('0' for new position)"),
10
+ orderType: z.number().int().min(0).max(3).describe("OrderType enum value"),
11
+ triggerType: z.number().int().min(0).max(2).describe("TriggerType enum value"),
12
+ direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT"),
13
+ collateralAmount: z.string().regex(/^\d+$/).describe("Quote token raw units"),
14
+ size: z.string().regex(/^\d+$/).describe("Position size raw units"),
15
+ price: z.string().regex(/^\d+$/).describe("Price raw units (30 decimals)"),
16
+ timeInForce: z.number().int().describe("TimeInForce enum value"),
17
+ postOnly: z.boolean().describe("Post-only flag"),
18
+ slippagePct: z.string().regex(/^\d+$/).describe("Slippage with 4-dec precision raw units"),
19
+ executionFeeToken: z.string().describe("Execution fee token address"),
20
+ leverage: z.number().describe("Leverage"),
21
+ tpSize: z.string().regex(/^\d+$/).optional().describe("TP size raw units"),
22
+ tpPrice: z.string().regex(/^\d+$/).optional().describe("TP price raw units (30 decimals)"),
23
+ slSize: z.string().regex(/^\d+$/).optional().describe("SL size raw units"),
24
+ slPrice: z.string().regex(/^\d+$/).optional().describe("SL price raw units (30 decimals)"),
25
+ tradingFee: z.string().regex(/^\d+$/).describe("Trading fee raw units"),
26
+ marketId: z.string().describe("Market ID"),
21
27
  },
22
28
  handler: async (args) => {
23
29
  try {
@@ -4,7 +4,7 @@ export const getAccountVipInfoTool = {
4
4
  name: "get_account_vip_info",
5
5
  description: "Get account VIP/fee-tier information.",
6
6
  schema: {
7
- chainId: z.coerce.number().optional().describe("Optional chainId override"),
7
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
8
8
  },
9
9
  handler: async (args) => {
10
10
  try {
@@ -2,16 +2,16 @@ import { z } from "zod";
2
2
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  export const getBaseDetailTool = {
4
4
  name: "get_base_detail",
5
- description: "Get base asset detail metrics for a pool.",
5
+ description: "Get base token details.",
6
6
  schema: {
7
- poolId: z.string().describe("Pool ID"),
8
- chainId: z.coerce.number().optional().describe("Optional chainId override"),
7
+ baseAddress: z.string().describe("Base token address"),
8
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
9
9
  },
10
10
  handler: async (args) => {
11
11
  try {
12
12
  const { client } = await resolveClient();
13
13
  const chainId = args.chainId ?? getChainId();
14
- const result = await client.markets.getBaseDetail({ chainId, poolId: args.poolId });
14
+ const result = await client.markets.getBaseDetail({ chainId, baseAddress: args.baseAddress });
15
15
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
16
16
  }
17
17
  catch (error) {
@@ -1,12 +1,13 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
+ const klineIntervalSchema = z.enum(["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "1M"]);
3
4
  export const getKlineTool = {
4
5
  name: "get_kline",
5
6
  description: "Get K-line / candlestick data for a pool. Essential for price analysis.",
6
7
  schema: {
7
8
  poolId: z.string().describe("Pool ID"),
8
- interval: z.string().describe("K-line interval: '1m','5m','15m','30m','1h','4h','1d','1w','1M'"),
9
- limit: z.coerce.number().optional().describe("Number of bars (default 100)"),
9
+ interval: klineIntervalSchema.describe("K-line interval"),
10
+ limit: z.number().int().positive().optional().describe("Number of bars (default 100)"),
10
11
  },
11
12
  handler: async (args) => {
12
13
  try {
@@ -1,11 +1,12 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
+ const klineIntervalSchema = z.enum(["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "1M"]);
3
4
  export const getKlineLatestBarTool = {
4
5
  name: "get_kline_latest_bar",
5
6
  description: "Get the latest single K-line/candlestick bar for a pool.",
6
7
  schema: {
7
8
  poolId: z.string().describe("Pool ID"),
8
- interval: z.string().describe("K-line interval: '1m','5m','15m','30m','1h','4h','1d','1w','1M'"),
9
+ interval: klineIntervalSchema.describe("K-line interval"),
9
10
  },
10
11
  handler: async (args) => {
11
12
  try {
@@ -5,7 +5,7 @@ export const getMarketListTool = {
5
5
  name: "get_market_list",
6
6
  description: "Get tradable markets/pools (state=2 Active). Supports configurable result limit; backend may still enforce its own cap.",
7
7
  schema: {
8
- limit: z.coerce.number().optional().describe("Max results to request (default 1000)."),
8
+ limit: z.number().int().positive().optional().describe("Max results to request (default 1000)."),
9
9
  },
10
10
  handler: async (args) => {
11
11
  try {
@@ -1,16 +1,18 @@
1
1
  import { z } from "zod";
2
- import { resolveClient } from "../auth/resolveClient.js";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  import { getMarketPrice } from "../services/marketService.js";
4
4
  export const getMarketPriceTool = {
5
5
  name: "get_market_price",
6
6
  description: "Get the current market price for a specific pool.",
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID to get price for"),
9
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
9
10
  },
10
11
  handler: async (args) => {
11
12
  try {
12
13
  const { client } = await resolveClient();
13
- const data = await getMarketPrice(client, args.poolId);
14
+ const chainId = args.chainId ?? getChainId();
15
+ const data = await getMarketPrice(client, args.poolId, chainId);
14
16
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
15
17
  }
16
18
  catch (error) {
@@ -5,7 +5,7 @@ export const getNetworkFeeTool = {
5
5
  description: "Estimate network fee requirements for a market.",
6
6
  schema: {
7
7
  marketId: z.string().describe("Market ID"),
8
- chainId: z.coerce.number().optional().describe("Optional chainId override"),
8
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
9
9
  },
10
10
  handler: async (args) => {
11
11
  try {
@@ -1,16 +1,18 @@
1
1
  import { z } from "zod";
2
- import { resolveClient } from "../auth/resolveClient.js";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  import { getOraclePrice } from "../services/marketService.js";
4
4
  export const getOraclePriceTool = {
5
5
  name: "get_oracle_price",
6
6
  description: "Get the current oracle price for a specific pool.",
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID to get oracle price for"),
9
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
9
10
  },
10
11
  handler: async (args) => {
11
12
  try {
12
13
  const { client } = await resolveClient();
13
- const data = await getOraclePrice(client, args.poolId);
14
+ const chainId = args.chainId ?? getChainId();
15
+ const data = await getOraclePrice(client, args.poolId, chainId);
14
16
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
15
17
  }
16
18
  catch (error) {
@@ -4,9 +4,9 @@ export const getUserTradingFeeRateTool = {
4
4
  name: "get_user_trading_fee_rate",
5
5
  description: "Get maker/taker fee rates for a given assetClass and riskTier.",
6
6
  schema: {
7
- assetClass: z.coerce.number().describe("Asset class ID"),
8
- riskTier: z.coerce.number().describe("Risk tier"),
9
- chainId: z.coerce.number().optional().describe("Optional chainId override"),
7
+ assetClass: z.number().int().nonnegative().describe("Asset class ID"),
8
+ riskTier: z.number().int().nonnegative().describe("Risk tier"),
9
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
10
10
  },
11
11
  handler: async (args) => {
12
12
  try {
@@ -1,39 +1,30 @@
1
1
  import { z } from "zod";
2
2
  import { quoteDeposit, quoteWithdraw, baseDeposit, baseWithdraw, getLpPrice, } from "../services/poolService.js";
3
- import { parseSafeNumber } from "../utils/units.js";
4
3
  export const manageLiquidityTool = {
5
4
  name: "manage_liquidity",
6
- description: "Add or withdraw liquidity from a BASE or QUOTE pool. Amount is in human-readable units (e.g. 2000 for USDC, 0.01 for ETH).",
5
+ description: "Add or withdraw liquidity from a BASE or QUOTE pool.",
7
6
  schema: {
8
- action: z.string().trim().toLowerCase().refine((value) => value === "deposit" || value === "withdraw", {
9
- message: "action must be 'deposit' or 'withdraw'.",
10
- }).describe("'deposit' or 'withdraw'"),
11
- poolType: z.string().trim().toUpperCase().refine((value) => value === "BASE" || value === "QUOTE", {
12
- message: "poolType must be 'BASE' or 'QUOTE'.",
13
- }).describe("'BASE' or 'QUOTE'"),
7
+ action: z.enum(["deposit", "withdraw"]).describe("'deposit' or 'withdraw'"),
8
+ poolType: z.enum(["BASE", "QUOTE"]).describe("'BASE' or 'QUOTE'"),
14
9
  poolId: z.string().describe("Pool ID"),
15
- amount: z.union([z.number(), z.string()]).describe("Amount in human-readable units (e.g. 2000 for USDC, 0.01 for ETH)"),
16
- slippage: z.union([z.number(), z.string()]).describe("Slippage tolerance (e.g. 0.01 = 1%)"),
10
+ amount: z.number().positive().describe("Amount in human-readable units"),
11
+ slippage: z.number().min(0).describe("LP slippage ratio (e.g. 0.01 = 1%)"),
12
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
17
13
  },
18
14
  handler: async (args) => {
19
15
  try {
20
16
  const { action, poolType, poolId } = args;
21
- const amount = parseSafeNumber(args.amount, "amount");
22
- const slippage = parseSafeNumber(args.slippage, "slippage");
23
- if (amount <= 0)
24
- throw new Error("amount must be a positive number.");
25
- if (slippage < 0)
26
- throw new Error("slippage must be a non-negative number.");
17
+ const { amount, slippage } = args;
27
18
  let result;
28
19
  if (poolType === "QUOTE") {
29
20
  result = action === "deposit"
30
- ? await quoteDeposit(poolId, amount, slippage)
31
- : await quoteWithdraw(poolId, amount, slippage);
21
+ ? await quoteDeposit(poolId, amount, slippage, args.chainId)
22
+ : await quoteWithdraw(poolId, amount, slippage, args.chainId);
32
23
  }
33
24
  else {
34
25
  result = action === "deposit"
35
- ? await baseDeposit(poolId, amount, slippage)
36
- : await baseWithdraw(poolId, amount, slippage);
26
+ ? await baseDeposit(poolId, amount, slippage, args.chainId)
27
+ : await baseWithdraw(poolId, amount, slippage, args.chainId);
37
28
  }
38
29
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
39
30
  }
@@ -46,14 +37,13 @@ export const getLpPriceTool = {
46
37
  name: "get_lp_price",
47
38
  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.",
48
39
  schema: {
49
- poolType: z.string().trim().toUpperCase().refine((value) => value === "BASE" || value === "QUOTE", {
50
- message: "poolType must be 'BASE' or 'QUOTE'.",
51
- }).describe("'BASE' or 'QUOTE'"),
40
+ poolType: z.enum(["BASE", "QUOTE"]).describe("'BASE' or 'QUOTE'"),
52
41
  poolId: z.string().describe("Pool ID"),
42
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
53
43
  },
54
44
  handler: async (args) => {
55
45
  try {
56
- const data = await getLpPrice(args.poolType, args.poolId);
46
+ const data = await getLpPrice(args.poolType, args.poolId, args.chainId);
57
47
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
58
48
  }
59
49
  catch (error) {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { resolveClient } from "../auth/resolveClient.js";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  import { getMarketDetail } from "../services/marketService.js";
4
4
  import { getPoolInfo, getLiquidityInfo } from "../services/poolService.js";
5
5
  export const getMarketDetailTool = {
@@ -7,11 +7,13 @@ export const getMarketDetailTool = {
7
7
  description: "Get detailed information for a specific trading pool (fee rates, open interest, etc.).",
8
8
  schema: {
9
9
  poolId: z.string().describe("Pool ID"),
10
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
10
11
  },
11
12
  handler: async (args) => {
12
13
  try {
13
14
  const { client } = await resolveClient();
14
- const data = await getMarketDetail(client, args.poolId);
15
+ const chainId = args.chainId ?? getChainId();
16
+ const data = await getMarketDetail(client, args.poolId, chainId);
15
17
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
16
18
  }
17
19
  catch (error) {
@@ -24,10 +26,12 @@ export const getPoolInfoTool = {
24
26
  description: "Get pool on-chain information (reserves, utilization, etc.).",
25
27
  schema: {
26
28
  poolId: z.string().describe("Pool ID"),
29
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
27
30
  },
28
31
  handler: async (args) => {
29
32
  try {
30
- const data = await getPoolInfo(args.poolId);
33
+ const chainId = args.chainId ?? getChainId();
34
+ const data = await getPoolInfo(args.poolId, chainId);
31
35
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
32
36
  }
33
37
  catch (error) {
@@ -40,12 +44,14 @@ export const getLiquidityInfoTool = {
40
44
  description: "Get pool liquidity utilization and depth information.",
41
45
  schema: {
42
46
  poolId: z.string().describe("Pool ID"),
43
- marketPrice: z.string().describe("Current market price (can be human-readable e.g. '0.69' or 30-decimal raw units)"),
47
+ marketPrice: z.string().regex(/^\d+$/).describe("Current market price in 30-decimal raw units"),
48
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
44
49
  },
45
50
  handler: async (args) => {
46
51
  try {
47
52
  const { client } = await resolveClient();
48
- const data = await getLiquidityInfo(client, args.poolId, args.marketPrice);
53
+ const chainId = args.chainId ?? getChainId();
54
+ const data = await getLiquidityInfo(client, args.poolId, args.marketPrice, chainId);
49
55
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
50
56
  }
51
57
  catch (error) {
@@ -24,17 +24,16 @@ export const getOpenOrdersTool = {
24
24
  };
25
25
  export const getOrderHistoryTool = {
26
26
  name: "get_order_history",
27
- description: "Get historical orders with optional pool filter and pagination.",
27
+ description: "Get historical orders with optional pool filter.",
28
28
  schema: {
29
29
  poolId: z.string().optional().describe("Filter by pool ID"),
30
- page: z.coerce.number().optional().describe("Page number (default 1)"),
31
- limit: z.coerce.number().optional().describe("Results per page (default 20)"),
30
+ limit: z.number().int().positive().optional().describe("Results per page (default 20)"),
32
31
  },
33
32
  handler: async (args) => {
34
33
  try {
35
34
  const { client, address } = await resolveClient();
36
35
  const chainId = getChainId();
37
- const query = { chainId, poolId: args.poolId, page: args.page ?? 1, limit: args.limit ?? 20 };
36
+ const query = { chainId, poolId: args.poolId, limit: args.limit ?? 20 };
38
37
  const result = await client.order.getOrderHistory(query, address);
39
38
  const enhancedData = (result?.data || []).map((order) => ({
40
39
  ...order,
@@ -1,16 +1,18 @@
1
1
  import { z } from "zod";
2
- import { resolveClient } from "../auth/resolveClient.js";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  import { getPoolLevelConfig } from "../services/marketService.js";
4
4
  export const getPoolLevelConfigTool = {
5
5
  name: "get_pool_level_config",
6
6
  description: "Get the level configuration and trading limits for a specific pool.",
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID"),
9
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
9
10
  },
10
11
  handler: async (args) => {
11
12
  try {
12
13
  const { client } = await resolveClient();
13
- const result = await getPoolLevelConfig(client, args.poolId);
14
+ const chainId = args.chainId ?? getChainId();
15
+ const result = await getPoolLevelConfig(client, args.poolId, chainId);
14
16
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
15
17
  }
16
18
  catch (error) {
@@ -3,17 +3,16 @@ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  import { getDirectionDesc, getCloseTypeDesc } from "../utils/mappings.js";
4
4
  export const getPositionHistoryTool = {
5
5
  name: "get_position_history",
6
- description: "Get historical closed positions with optional pool filter and pagination.",
6
+ description: "Get historical closed positions with optional pool filter.",
7
7
  schema: {
8
8
  poolId: z.string().optional().describe("Filter by pool ID"),
9
- page: z.coerce.number().optional().describe("Page number (default 1)"),
10
- limit: z.coerce.number().optional().describe("Results per page (default 20)"),
9
+ limit: z.number().int().positive().optional().describe("Results per page (default 20)"),
11
10
  },
12
11
  handler: async (args) => {
13
12
  try {
14
13
  const { client, address } = await resolveClient();
15
14
  const chainId = getChainId();
16
- const query = { chainId, poolId: args.poolId, page: args.page ?? 1, limit: args.limit ?? 20 };
15
+ const query = { chainId, poolId: args.poolId, limit: args.limit ?? 20 };
17
16
  const result = await client.position.getPositionHistory(query, address);
18
17
  const enhancedData = (result?.data || []).map((pos) => ({
19
18
  ...pos,
@@ -6,7 +6,7 @@ export const searchMarketTool = {
6
6
  description: "Search for an active market by keyword.",
7
7
  schema: {
8
8
  keyword: z.string().describe('Search keyword, e.g. "BTC", "ETH"'),
9
- limit: z.coerce.number().optional().describe("Max results (default 100)"),
9
+ limit: z.number().int().positive().optional().describe("Max results (default 100)"),
10
10
  },
11
11
  handler: async (args) => {
12
12
  try {
@@ -3,19 +3,20 @@ import { resolveClient } from "../auth/resolveClient.js";
3
3
  import { setPositionTpSl } from "../services/tradeService.js";
4
4
  export const setTpSlTool = {
5
5
  name: "set_tp_sl",
6
- description: "Set take profit and stop loss prices for an open position.",
6
+ description: "Create TP/SL order using SDK-native parameters.",
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID"),
9
9
  positionId: z.string().describe("Position ID"),
10
- direction: z.coerce.number().int().refine((value) => value === 0 || value === 1, {
11
- message: "direction must be 0 (LONG) or 1 (SHORT)",
12
- }).describe("0 = LONG, 1 = SHORT (position direction)"),
13
- leverage: z.coerce.number().describe("Position leverage"),
14
- tpPrice: z.string().optional().describe("Take-profit trigger price (human-readable)"),
15
- tpSize: z.string().optional().describe("TP size (18 decimals). Omit or '0' for full position."),
16
- slPrice: z.string().optional().describe("Stop-loss trigger price (human-readable)"),
17
- slSize: z.string().optional().describe("SL size (18 decimals). Omit or '0' for full position."),
18
- quoteToken: z.string().optional().describe("Quote token address"),
10
+ direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT"),
11
+ leverage: z.number().describe("Leverage"),
12
+ executionFeeToken: z.string().describe("Execution fee token address"),
13
+ tpTriggerType: z.number().int().min(0).max(2).describe("TP trigger type"),
14
+ slTriggerType: z.number().int().min(0).max(2).describe("SL trigger type"),
15
+ slippagePct: z.string().regex(/^\d+$/).describe("Slippage with 4-dec precision raw units"),
16
+ tpPrice: z.string().regex(/^\d+$/).optional().describe("TP price raw units (30 decimals)"),
17
+ tpSize: z.string().regex(/^\d+$/).optional().describe("TP size raw units"),
18
+ slPrice: z.string().regex(/^\d+$/).optional().describe("SL price raw units (30 decimals)"),
19
+ slSize: z.string().regex(/^\d+$/).optional().describe("SL size raw units"),
19
20
  },
20
21
  handler: async (args) => {
21
22
  try {
@@ -1,19 +1,21 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient } from "../auth/resolveClient.js";
3
3
  import { updateOrderTpSl } from "../services/tradeService.js";
4
- import { booleanLike } from "../utils/schema.js";
5
4
  export const updateOrderTpSlTool = {
6
5
  name: "update_order_tp_sl",
7
6
  description: "Update an existing take profit or stop loss order.",
8
7
  schema: {
9
8
  orderId: z.string().describe("The ID of the order to update"),
10
9
  marketId: z.string().describe("The market ID (config hash) for the order"),
11
- tpPrice: z.string().optional().describe("New take-profit trigger price (human-readable)"),
12
- tpSize: z.string().optional().describe("New TP size (18 decimals). Omit or '0' for full position."),
13
- slPrice: z.string().optional().describe("New stop-loss trigger price (human-readable)"),
14
- slSize: z.string().optional().describe("New SL size (18 decimals). Omit or '0' for full position."),
15
- isTpSlOrder: booleanLike.optional().describe("Whether this is a TP/SL order (default true)"),
16
- quoteToken: z.string().optional().describe("Quote token address"),
10
+ size: z.string().regex(/^\d+$/).describe("Order size raw units"),
11
+ price: z.string().regex(/^\d+$/).describe("Order price raw units (30 decimals)"),
12
+ tpPrice: z.string().regex(/^\d+$/).describe("TP price raw units (30 decimals)"),
13
+ tpSize: z.string().regex(/^\d+$/).describe("TP size raw units"),
14
+ slPrice: z.string().regex(/^\d+$/).describe("SL price raw units (30 decimals)"),
15
+ slSize: z.string().regex(/^\d+$/).describe("SL size raw units"),
16
+ useOrderCollateral: z.boolean().describe("Whether to use order collateral"),
17
+ isTpSlOrder: z.boolean().optional().describe("Whether this is a TP/SL order"),
18
+ quoteToken: z.string().describe("Quote token address"),
17
19
  },
18
20
  handler: async (args) => {
19
21
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michaleffffff/mcp-trading-server",
3
- "version": "2.3.6",
3
+ "version": "2.4.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "myx-mcp": "dist/server.js"
@@ -1,18 +0,0 @@
1
- import { z } from "zod";
2
- const trueValues = new Set(["true", "1", "yes", "y", "on"]);
3
- const falseValues = new Set(["false", "0", "no", "n", "off"]);
4
- /**
5
- * Safe boolean coercion for MCP inputs:
6
- * - boolean: true/false
7
- * - number: 1/0
8
- * - string: true/false/1/0/yes/no/on/off (case-insensitive)
9
- */
10
- export const booleanLike = z.union([
11
- z.boolean(),
12
- z.number().refine((value) => value === 0 || value === 1, {
13
- message: "Expected 0 or 1 for boolean value.",
14
- }).transform((value) => value === 1),
15
- z.string().trim().toLowerCase().refine((value) => trueValues.has(value) || falseValues.has(value), {
16
- message: "Expected boolean-like value: true/false/1/0/yes/no/on/off.",
17
- }).transform((value) => trueValues.has(value)),
18
- ]);