@michaleffffff/mcp-trading-server 3.0.4 → 3.0.7

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 (55) hide show
  1. package/CHANGELOG.md +42 -6
  2. package/README.md +49 -414
  3. package/TOOL_EXAMPLES.md +63 -559
  4. package/dist/auth/resolveClient.js +19 -2
  5. package/dist/prompts/tradingGuide.js +16 -23
  6. package/dist/server.js +20 -2
  7. package/dist/services/balanceService.js +4 -4
  8. package/dist/services/marketService.js +48 -12
  9. package/dist/services/poolService.js +45 -44
  10. package/dist/services/tradeService.js +51 -23
  11. package/dist/tools/accountInfo.js +5 -134
  12. package/dist/tools/accountTransfer.js +7 -12
  13. package/dist/tools/adjustMargin.js +1 -1
  14. package/dist/tools/cancelOrders.js +71 -0
  15. package/dist/tools/checkAccountReady.js +75 -0
  16. package/dist/tools/checkApproval.js +1 -1
  17. package/dist/tools/closeAllPositions.js +9 -7
  18. package/dist/tools/closePosition.js +18 -11
  19. package/dist/tools/createPerpMarket.js +1 -1
  20. package/dist/tools/executeTrade.js +6 -6
  21. package/dist/tools/findPool.js +26 -0
  22. package/dist/tools/getAccountSnapshot.js +47 -0
  23. package/dist/tools/getAllTickers.js +5 -4
  24. package/dist/tools/getBaseDetail.js +1 -1
  25. package/dist/tools/getKline.js +7 -4
  26. package/dist/tools/getMyLpHoldings.js +3 -2
  27. package/dist/tools/getNetworkFee.js +1 -1
  28. package/dist/tools/getOrders.js +46 -0
  29. package/dist/tools/getPoolMetadata.js +95 -0
  30. package/dist/tools/getPositionsAll.js +80 -0
  31. package/dist/tools/{getMarketPrice.js → getPrice.js} +8 -5
  32. package/dist/tools/getUserTradingFeeRate.js +66 -3
  33. package/dist/tools/index.js +15 -19
  34. package/dist/tools/listPools.js +53 -0
  35. package/dist/tools/manageLiquidity.js +4 -4
  36. package/dist/tools/manageTpSl.js +234 -0
  37. package/dist/tools/openPositionSimple.js +27 -8
  38. package/dist/tools/searchTools.js +35 -0
  39. package/dist/utils/mappings.js +10 -7
  40. package/package.json +2 -2
  41. package/dist/tools/cancelAllOrders.js +0 -57
  42. package/dist/tools/cancelOrder.js +0 -22
  43. package/dist/tools/getAccountVipInfo.js +0 -20
  44. package/dist/tools/getKlineLatestBar.js +0 -28
  45. package/dist/tools/getOraclePrice.js +0 -22
  46. package/dist/tools/getPoolList.js +0 -17
  47. package/dist/tools/getPoolSymbolAll.js +0 -16
  48. package/dist/tools/getPositions.js +0 -77
  49. package/dist/tools/marketInfo.js +0 -88
  50. package/dist/tools/orderQueries.js +0 -51
  51. package/dist/tools/poolConfig.js +0 -22
  52. package/dist/tools/positionHistory.js +0 -28
  53. package/dist/tools/searchMarket.js +0 -21
  54. package/dist/tools/setTpSl.js +0 -50
  55. package/dist/tools/updateOrderTpSl.js +0 -34
@@ -1,141 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
- import { getBalances, getMarginBalance } from "../services/balanceService.js";
4
3
  import { getTradeFlowTypeDesc } from "../utils/mappings.js";
5
- function readErrorMessage(error) {
6
- if (error instanceof Error)
7
- return error.message;
8
- if (typeof error === "string")
9
- return error;
10
- if (error && typeof error === "object" && "message" in error) {
11
- return String(error.message);
12
- }
13
- return String(error ?? "unknown error");
14
- }
15
- function assertSdkReadSuccess(result, actionName) {
16
- if (!result || typeof result !== "object" || Array.isArray(result))
17
- return;
18
- if (!Object.prototype.hasOwnProperty.call(result, "code"))
19
- return;
20
- const code = Number(result.code);
21
- if (!Number.isFinite(code) || code === 0)
22
- return;
23
- const payload = result.data;
24
- const hasPayload = payload !== null &&
25
- payload !== undefined &&
26
- (!Array.isArray(payload) || payload.length > 0);
27
- if (code > 0 && hasPayload) {
28
- // Keep aligned with server-side read normalization:
29
- // positive non-zero code with usable payload is warning-like.
30
- return;
31
- }
32
- const message = String(result.msg ?? result.message ?? "unknown error");
33
- throw new Error(`${actionName} failed: code=${code}, msg=${message}`);
34
- }
35
- function normalizeAccountInfo(result) {
36
- if (!(result && result.code === 0 && Array.isArray(result.data))) {
37
- return result;
38
- }
39
- const values = result.data;
40
- return {
41
- ...result,
42
- data: {
43
- freeMargin: values[0],
44
- walletBalance: values[1],
45
- freeBaseAmount: values[2],
46
- baseProfit: values[3],
47
- quoteProfit: values[4],
48
- reservedAmount: values[5],
49
- releaseTime: values[6],
50
- tradeableMargin: (BigInt(values[0]) + BigInt(values[1]) + (BigInt(values[6]) === 0n ? BigInt(values[4]) : 0n)).toString(),
51
- baseProfitStatus: "base token to be unlocked",
52
- quoteProfitStatus: BigInt(values[6]) > 0n ? "quote token to be unlocked" : "quote token unlocked/available"
53
- }
54
- };
55
- }
56
- export const getAccountTool = {
57
- name: "get_account",
58
- description: "Unified account snapshot. Distinguishes wallet balance vs trading-account metrics. Provide poolId for full trading-account details.",
59
- schema: {
60
- poolId: z.string().optional().describe("Optional Pool ID. Required to include trading-account metrics (account info + margin)."),
61
- },
62
- handler: async (args) => {
63
- try {
64
- const { client, address } = await resolveClient();
65
- const chainId = getChainId();
66
- const poolId = args.poolId?.trim();
67
- const overview = {
68
- meta: {
69
- address,
70
- chainId,
71
- poolId: poolId ?? null,
72
- partial: false,
73
- },
74
- wallet: {
75
- scope: "wallet_balance",
76
- description: "On-chain wallet quote-token balance (funds not yet deposited into trading account).",
77
- quoteTokenBalance: null,
78
- raw: null,
79
- error: null,
80
- },
81
- tradingAccount: {
82
- scope: "trading_account_balance",
83
- description: poolId
84
- ? "Trading-account metrics for the selected pool."
85
- : "Provide poolId to include trading-account metrics (account info + margin).",
86
- poolId: poolId ?? null,
87
- accountInfo: null,
88
- marginBalance: null,
89
- error: null,
90
- },
91
- warnings: [],
92
- };
93
- try {
94
- const walletBalances = await getBalances(client, address);
95
- assertSdkReadSuccess(walletBalances, "getWalletQuoteTokenBalance");
96
- overview.wallet.quoteTokenBalance = walletBalances?.data ?? walletBalances;
97
- overview.wallet.raw = walletBalances;
98
- }
99
- catch (walletError) {
100
- overview.meta.partial = true;
101
- overview.wallet.error = readErrorMessage(walletError);
102
- overview.warnings.push(`wallet section failed: ${overview.wallet.error}`);
103
- }
104
- if (poolId) {
105
- try {
106
- const [accountInfoRaw, marginBalanceRaw] = await Promise.all([
107
- client.account.getAccountInfo(chainId, address, poolId),
108
- getMarginBalance(client, address, poolId),
109
- ]);
110
- assertSdkReadSuccess(accountInfoRaw, "getAccountInfo");
111
- assertSdkReadSuccess(marginBalanceRaw, "getAvailableMarginBalance");
112
- overview.tradingAccount.accountInfo = normalizeAccountInfo(accountInfoRaw);
113
- overview.tradingAccount.marginBalance = marginBalanceRaw;
114
- }
115
- catch (tradingError) {
116
- overview.meta.partial = true;
117
- overview.tradingAccount.error = readErrorMessage(tradingError);
118
- overview.warnings.push(`tradingAccount section failed: ${overview.tradingAccount.error}`);
119
- }
120
- }
121
- if (overview.warnings.length === 0) {
122
- delete overview.warnings;
123
- }
124
- const hasWalletData = overview.wallet.quoteTokenBalance !== null;
125
- const hasTradingData = overview.tradingAccount.accountInfo !== null || overview.tradingAccount.marginBalance !== null;
126
- if (!hasWalletData && !hasTradingData) {
127
- throw new Error("Failed to build account snapshot: wallet and trading-account sections both unavailable.");
128
- }
129
- return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: overview }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
130
- }
131
- catch (error) {
132
- return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
133
- }
134
- },
135
- };
136
4
  export const getTradeFlowTool = {
137
5
  name: "get_trade_flow",
138
- description: "Get account trade flow / transaction history.",
6
+ description: "[ACCOUNT] Get account trade flow / transaction history.",
139
7
  schema: {
140
8
  poolId: z.string().optional().describe("Optional pool ID filter."),
141
9
  limit: z.number().int().positive().optional().describe("Results per page (default 20)"),
@@ -145,7 +13,10 @@ export const getTradeFlowTool = {
145
13
  const { client, address } = await resolveClient();
146
14
  const chainId = getChainId();
147
15
  const query = { chainId, poolId: args.poolId, limit: args.limit ?? 20 };
148
- const result = await client.account.getTradeFlow(query, address);
16
+ const accessToken = typeof client?.getAccessToken === "function"
17
+ ? (await client.getAccessToken()) || ""
18
+ : "";
19
+ const result = await client.api.getTradeFlow({ ...query, address, accessToken });
149
20
  const enhancedData = (result?.data || []).map((flow) => ({
150
21
  ...flow,
151
22
  typeDesc: getTradeFlowTypeDesc(flow.type)
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { resolveClient, getChainId } from "../auth/resolveClient.js";
2
+ import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
3
3
  import { normalizeAddress } from "../utils/address.js";
4
4
  import { finalizeMutationResult } from "../utils/mutationResult.js";
5
5
  const MAX_UINT256 = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
@@ -39,10 +39,10 @@ function formatUnixTimestamp(timestamp) {
39
39
  }
40
40
  export const accountDepositTool = {
41
41
  name: "account_deposit",
42
- description: "Deposit funds from wallet into the MYX trading account.",
42
+ description: "[ACCOUNT] Deposit funds from wallet into the MYX trading account.",
43
43
  schema: {
44
44
  amount: z.union([z.string(), z.number()]).describe("Amount to deposit (human-readable or raw units)"),
45
- tokenAddress: z.string().describe("Token address"),
45
+ tokenAddress: z.string().optional().describe("Token address (optional, default: QUOTE_TOKEN_ADDRESS)"),
46
46
  autoApprove: z.coerce.boolean().optional().describe("If true, auto-approve token allowance when needed (default false)."),
47
47
  approveMax: z.coerce.boolean().optional().describe("If autoApprove=true, approve MaxUint256 instead of exact amount."),
48
48
  },
@@ -50,7 +50,8 @@ export const accountDepositTool = {
50
50
  try {
51
51
  const { client, signer, address } = await resolveClient();
52
52
  const chainId = getChainId();
53
- const tokenAddress = normalizeAddress(args.tokenAddress, "tokenAddress");
53
+ const tokenAddressInput = String(args.tokenAddress ?? "").trim() || getQuoteToken();
54
+ const tokenAddress = normalizeAddress(tokenAddressInput, "tokenAddress");
54
55
  // For deposit, we default to quote decimals (6) as it's the most common use case.
55
56
  // ensureUnits handles 'raw:' prefix if absolute precision is needed.
56
57
  const { ensureUnits } = await import("../utils/units.js");
@@ -90,7 +91,7 @@ export const accountDepositTool = {
90
91
  };
91
92
  export const accountWithdrawTool = {
92
93
  name: "account_withdraw",
93
- description: "Withdraw funds from MYX trading account back to wallet.",
94
+ description: "[ACCOUNT] Withdraw funds from MYX trading account back to wallet.",
94
95
  schema: {
95
96
  poolId: z.string().describe("Pool ID to withdraw from"),
96
97
  amount: z.union([z.string(), z.number()]).describe("Amount to withdraw (human-readable or raw units)"),
@@ -128,13 +129,7 @@ export const accountWithdrawTool = {
128
129
  throw new Error(`Account has locked funds until releaseTime=${formatUnixTimestamp(releaseTime)}. ` +
129
130
  `Retry after unlock or reduce withdraw amount.`);
130
131
  }
131
- const raw = await client.account.withdraw({
132
- chainId,
133
- receiver: address,
134
- amount,
135
- poolId: args.poolId,
136
- isQuoteToken: args.isQuoteToken,
137
- });
132
+ const raw = await client.account.updateAndWithdraw(address, args.poolId, Boolean(args.isQuoteToken), amount, chainId);
138
133
  const data = await finalizeMutationResult(raw, signer, "account_withdraw");
139
134
  const preflight = {
140
135
  requestedAmountRaw: amountRaw.toString(),
@@ -5,7 +5,7 @@ import { finalizeMutationResult } from "../utils/mutationResult.js";
5
5
  import { extractErrorMessage } from "../utils/errorMessage.js";
6
6
  export const adjustMarginTool = {
7
7
  name: "adjust_margin",
8
- description: "Adjust the margin (collateral) of an open position.",
8
+ description: "[TRADE] Adjust the margin (collateral) of an open position.",
9
9
  schema: {
10
10
  poolId: z.string().describe("Pool ID"),
11
11
  positionId: z.string().describe("Position ID"),
@@ -0,0 +1,71 @@
1
+ import { z } from "zod";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
+ import { resolvePool } from "../services/marketService.js";
4
+ import { finalizeMutationResult } from "../utils/mutationResult.js";
5
+ function normalizeOrderIds(input) {
6
+ if (Array.isArray(input))
7
+ return input.map((id) => String(id).trim()).filter(Boolean);
8
+ if (typeof input !== "string")
9
+ return [];
10
+ const text = input.trim();
11
+ if (!text)
12
+ return [];
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
+ catch { }
20
+ }
21
+ if (text.includes(","))
22
+ return text.split(",").map((id) => id.trim()).filter(Boolean);
23
+ return [text];
24
+ }
25
+ export const cancelOrdersTool = {
26
+ name: "cancel_orders",
27
+ description: "[TRADE] Cancel open orders. Supports specific IDs, all orders in a pool, or ALL open orders.",
28
+ schema: {
29
+ orderIds: z.union([z.array(z.string()), z.string()]).optional().describe("Specific order ID(s) to cancel."),
30
+ poolId: z.string().optional().describe("Pool ID or keyword to cancel all orders within."),
31
+ keyword: z.string().optional().describe("Market keyword (e.g. 'BTC') for pool-based cancellation."),
32
+ cancelAll: z.boolean().default(false).describe("If true, cancel ALL open orders for the account."),
33
+ },
34
+ handler: async (args) => {
35
+ try {
36
+ const { client, address, signer } = await resolveClient();
37
+ const chainId = getChainId();
38
+ let targetOrderIds = normalizeOrderIds(args.orderIds);
39
+ if (args.cancelAll || args.poolId || args.keyword) {
40
+ const ordersRes = await client.order.getOrders(address);
41
+ const allOrders = ordersRes?.data || [];
42
+ if (args.cancelAll) {
43
+ targetOrderIds = allOrders.map((o) => o.orderId);
44
+ }
45
+ else {
46
+ const poolId = await resolvePool(client, args.poolId, args.keyword);
47
+ targetOrderIds = allOrders
48
+ .filter((o) => String(o.poolId).toLowerCase() === poolId.toLowerCase())
49
+ .map((o) => o.orderId);
50
+ }
51
+ }
52
+ if (targetOrderIds.length === 0) {
53
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: "No matching open orders found to cancel." }) }] };
54
+ }
55
+ const raw = await client.order.cancelAllOrders(targetOrderIds, chainId);
56
+ const result = await finalizeMutationResult(raw, signer, "cancel_orders");
57
+ return {
58
+ content: [{
59
+ type: "text",
60
+ text: JSON.stringify({
61
+ status: "success",
62
+ data: { cancelledCount: targetOrderIds.length, orderIds: targetOrderIds, result }
63
+ }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)
64
+ }]
65
+ };
66
+ }
67
+ catch (error) {
68
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
69
+ }
70
+ },
71
+ };
@@ -0,0 +1,75 @@
1
+ import { z } from "zod";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
+ import { resolvePool } from "../services/marketService.js";
4
+ import { parseUserUnits } from "../utils/units.js";
5
+ import { ethers } from "ethers";
6
+ function toBigIntOrZero(value) {
7
+ try {
8
+ const text = String(value ?? "").trim();
9
+ if (!text)
10
+ return 0n;
11
+ return BigInt(text);
12
+ }
13
+ catch {
14
+ return 0n;
15
+ }
16
+ }
17
+ export const checkAccountReadyTool = {
18
+ name: "check_account_ready",
19
+ description: "[TRADE] Check if the account has sufficient funds (margin + wallet) for a planned trade.",
20
+ schema: {
21
+ poolId: z.string().optional().describe("Pool ID or keyword."),
22
+ keyword: z.string().optional().describe("Market keyword, e.g. 'BTC'."),
23
+ collateralAmount: z.string().describe("Planned collateral, e.g. '100'."),
24
+ },
25
+ handler: async (args) => {
26
+ try {
27
+ const { client, address } = await resolveClient();
28
+ const chainId = getChainId();
29
+ const poolId = await resolvePool(client, args.poolId, args.keyword);
30
+ const detailRes = await client.markets.getMarketDetail({ chainId, poolId });
31
+ const detail = detailRes?.data || detailRes;
32
+ const quoteDecimals = Number(detail?.quoteDecimals ?? 6);
33
+ const quoteSymbol = detail?.quoteSymbol || "USDC";
34
+ const marginInfo = await client.account.getAccountInfo(chainId, address, poolId);
35
+ let marginBalanceRaw = 0n;
36
+ let accountWalletBalanceRaw = 0n;
37
+ if (marginInfo?.code === 0 && marginInfo?.data) {
38
+ marginBalanceRaw = toBigIntOrZero(marginInfo.data.freeMargin);
39
+ accountWalletBalanceRaw = toBigIntOrZero(marginInfo.data.walletBalance);
40
+ }
41
+ const walletRes = await client.account.getWalletQuoteTokenBalance(chainId, address);
42
+ const walletBalanceRaw = BigInt(walletRes?.data || "0");
43
+ const requiredRaw = BigInt(parseUserUnits(args.collateralAmount, quoteDecimals, "required"));
44
+ const isReady = (marginBalanceRaw >= requiredRaw) || (marginBalanceRaw + walletBalanceRaw >= requiredRaw);
45
+ const deficitRaw = requiredRaw > marginBalanceRaw ? requiredRaw - marginBalanceRaw : 0n;
46
+ const needDepositFromWallet = deficitRaw > 0n;
47
+ const walletSufficient = walletBalanceRaw >= deficitRaw;
48
+ const format = (v) => ethers.formatUnits(v, quoteDecimals);
49
+ return {
50
+ content: [{
51
+ type: "text",
52
+ text: JSON.stringify({
53
+ status: "success",
54
+ data: {
55
+ isReady,
56
+ neededTotal: format(requiredRaw),
57
+ currentMarginBalance: format(marginBalanceRaw),
58
+ currentWalletBalance: format(walletBalanceRaw),
59
+ summary: {
60
+ hasEnoughInMargin: marginBalanceRaw >= requiredRaw,
61
+ needDepositFromWallet,
62
+ walletSufficientForDeposit: walletSufficient,
63
+ accountInfoWalletBalance: format(accountWalletBalanceRaw),
64
+ quoteSymbol
65
+ }
66
+ }
67
+ }, null, 2)
68
+ }]
69
+ };
70
+ }
71
+ catch (error) {
72
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
73
+ }
74
+ },
75
+ };
@@ -5,7 +5,7 @@ import { finalizeMutationResult } from "../utils/mutationResult.js";
5
5
  const MAX_UINT256 = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
6
6
  export const checkApprovalTool = {
7
7
  name: "check_approval",
8
- description: "Check if token spending approval is needed. Supports auto-approve exact amount (default) or optional unlimited approval.",
8
+ description: "[TRADE] Check if token spending approval is needed. Supports auto-approve exact amount (default) or optional unlimited approval.",
9
9
  schema: {
10
10
  amount: z.string().regex(/^\d+$/).describe("Amount to check approval for (token raw units)"),
11
11
  quoteToken: z.string().optional().describe("Token address to check. Uses default if omitted."),
@@ -1,26 +1,28 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
3
- import { getOraclePrice } from "../services/marketService.js";
3
+ import { getOraclePrice, resolvePool } from "../services/marketService.js";
4
4
  import { ensureUnits } from "../utils/units.js";
5
5
  import { finalizeMutationResult } from "../utils/mutationResult.js";
6
6
  import { normalizeSlippagePct4dpFlexible, SLIPPAGE_PCT_4DP_DESC } from "../utils/slippage.js";
7
7
  export const closeAllPositionsTool = {
8
8
  name: "close_all_positions",
9
- description: "Emergency: close ALL open positions in a pool at once. Use for risk management.",
9
+ description: "[TRADE] Emergency: close ALL open positions in a pool at once. Use for risk management.",
10
10
  schema: {
11
- poolId: z.string().describe("Pool ID to close all positions in"),
11
+ poolId: z.string().optional().describe("Pool ID or keyword."),
12
+ keyword: z.string().optional().describe("Market keyword, e.g. 'BTC'."),
12
13
  slippagePct: z.union([z.string(), z.number()]).optional().describe(`${SLIPPAGE_PCT_4DP_DESC}. Also supports human percent format like "1.0" or "1%".`),
13
14
  },
14
15
  handler: async (args) => {
15
16
  try {
16
17
  const { client, address, signer } = await resolveClient();
17
18
  const chainId = getChainId();
19
+ const poolId = await resolvePool(client, args.poolId, args.keyword);
18
20
  // 1) 先获取该池的所有持仓
19
21
  const posResult = await client.position.listPositions(address);
20
22
  const positions = posResult?.data || posResult || [];
21
23
  // 过滤出指定 pool 的仓位
22
24
  const poolPositions = Array.isArray(positions)
23
- ? positions.filter((p) => p.poolId === args.poolId || p.pool_id === args.poolId)
25
+ ? positions.filter((p) => p.poolId === poolId || p.pool_id === poolId)
24
26
  : [];
25
27
  if (poolPositions.length === 0) {
26
28
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: { message: "No open positions in this pool.", closed: 0 } }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
@@ -28,14 +30,14 @@ export const closeAllPositionsTool = {
28
30
  // 2) 为每个仓位构建平仓参数
29
31
  const slippagePct = normalizeSlippagePct4dpFlexible(args.slippagePct ?? "200");
30
32
  // 3) We need actual oracle prices to avoid Revert 0x613970e0 (InvalidParameter)
31
- const oraclePriceReq = await getOraclePrice(client, args.poolId).catch(() => null);
33
+ const oraclePriceReq = await getOraclePrice(client, poolId).catch(() => null);
32
34
  let fallbackPrice = "0";
33
35
  if (oraclePriceReq && oraclePriceReq.price) {
34
36
  fallbackPrice = oraclePriceReq.price.toString();
35
37
  }
36
38
  else {
37
39
  const { getMarketPrice } = await import("../services/marketService.js");
38
- const marketData = await getMarketPrice(client, args.poolId).catch(() => null);
40
+ const marketData = await getMarketPrice(client, poolId).catch(() => null);
39
41
  if (marketData && marketData.price) {
40
42
  fallbackPrice = marketData.price.toString();
41
43
  }
@@ -50,7 +52,7 @@ export const closeAllPositionsTool = {
50
52
  return {
51
53
  chainId,
52
54
  address,
53
- poolId: args.poolId,
55
+ poolId: poolId,
54
56
  positionId: pos.positionId || pos.position_id || pos.id,
55
57
  orderType: 0, // MARKET
56
58
  triggerType: 0, // NONE
@@ -5,26 +5,33 @@ import { finalizeMutationResult } from "../utils/mutationResult.js";
5
5
  import { SLIPPAGE_PCT_4DP_DESC } from "../utils/slippage.js";
6
6
  import { verifyTradeOutcome } from "../utils/verification.js";
7
7
  import { mapDirection, mapOrderType, mapTriggerType } from "../utils/mappings.js";
8
+ import { parseUserUnits } from "../utils/units.js";
8
9
  const FULL_CLOSE_MARKERS = new Set(["ALL", "FULL", "MAX"]);
9
10
  const INTEGER_RE = /^\d+$/;
10
11
  function wantsFullCloseMarker(input) {
11
12
  const value = String(input ?? "").trim().toUpperCase();
12
13
  return FULL_CLOSE_MARKERS.has(value);
13
14
  }
14
- function readRawPositionField(position, primary, fallback) {
15
- const first = String(position?.[primary] ?? "").trim();
16
- if (INTEGER_RE.test(first))
17
- return first;
18
- if (fallback) {
19
- const second = String(position?.[fallback] ?? "").trim();
20
- if (INTEGER_RE.test(second))
21
- return second;
15
+ function readFirstPositionField(position, fields) {
16
+ for (const field of fields) {
17
+ const value = String(position?.[field] ?? "").trim();
18
+ if (value)
19
+ return value;
22
20
  }
23
21
  return "";
24
22
  }
23
+ function resolvePositionRaw(position, rawFields, humanFields, decimals, label) {
24
+ const raw = readFirstPositionField(position, rawFields);
25
+ if (INTEGER_RE.test(raw))
26
+ return raw;
27
+ const human = readFirstPositionField(position, humanFields);
28
+ if (!human)
29
+ return "";
30
+ return parseUserUnits(human, decimals, label);
31
+ }
25
32
  export const closePositionTool = {
26
33
  name: "close_position",
27
- description: "Create a decrease order using SDK-native parameters.",
34
+ description: "[TRADE] Create a decrease order (close or reduce position) using SDK-native parameters.",
28
35
  schema: {
29
36
  poolId: z.string().describe("Pool ID"),
30
37
  positionId: z.string().describe("Position ID to close"),
@@ -67,14 +74,14 @@ export const closePositionTool = {
67
74
  throw new Error(`Could not find live position snapshot for positionId=${preparedArgs.positionId} in poolId=${poolId}.`);
68
75
  }
69
76
  if (needAutoSize) {
70
- const rawSize = readRawPositionField(target, "size", "positionSize");
77
+ const rawSize = resolvePositionRaw(target, ["sizeRaw", "positionSizeRaw"], ["size", "positionSize"], Number(poolData.baseDecimals ?? 18), "position.size");
71
78
  if (!rawSize || rawSize === "0") {
72
79
  throw new Error(`Resolved position size is empty/zero for positionId=${preparedArgs.positionId}.`);
73
80
  }
74
81
  preparedArgs.size = `raw:${rawSize}`;
75
82
  }
76
83
  if (needAutoCollateral) {
77
- const rawCollateral = readRawPositionField(target, "collateral", "collateralAmount");
84
+ const rawCollateral = resolvePositionRaw(target, ["collateralRaw", "collateralAmountRaw"], ["collateral", "collateralAmount"], Number(poolData.quoteDecimals ?? 6), "position.collateralAmount");
78
85
  if (!rawCollateral) {
79
86
  throw new Error(`Resolved position collateral is empty for positionId=${preparedArgs.positionId}.`);
80
87
  }
@@ -4,7 +4,7 @@ import { resolveClient } from "../auth/resolveClient.js";
4
4
  import { finalizeMutationResult } from "../utils/mutationResult.js";
5
5
  export const createPerpMarketTool = {
6
6
  name: "create_perp_market",
7
- description: "Create a new perpetual contract pool on MYX. IMPORTANT: marketId cannot be randomly generated. It must be a valid 66-character config hash (0x...) tied to a supported quote token (like USDC). Use get_market_detail (after search_market/get_pool_list) to fetch an existing marketId if you don't have a specific newly allocated one.",
7
+ description: "[LIQUIDITY] Create a new perpetual contract pool on MYX. IMPORTANT: marketId cannot be randomly generated. It must be a valid 66-character config hash (0x...) tied to a supported quote token (like USDC). Use get_pool_metadata (after find_pool/list_pools) to fetch an existing marketId if you don't have a specific newly allocated one.",
8
8
  schema: {
9
9
  baseToken: z.string().describe("Base token contract address (e.g., 0xb40aaadc43...)"),
10
10
  marketId: z.string().describe("MUST be a valid 66-char config hash (e.g., existing USDC marketId: 0x7f6727d8026fd2c87ccc745846c83cd0b68e886c73e1e05a54a675bcadd8adb6). Do NOT generate randomly."),
@@ -11,12 +11,12 @@ const POSITION_ID_RE = /^$|^0x[0-9a-fA-F]{64}$/;
11
11
  const ZERO_POSITION_ID_RE = /^0x0{64}$/i;
12
12
  export const executeTradeTool = {
13
13
  name: "execute_trade",
14
- description: "Create an increase order using SDK-native parameters.",
14
+ description: "[TRADE] Create an increase order (open or add to position) using SDK-native parameters.",
15
15
  schema: {
16
- poolId: z.string().describe("Hex Pool ID, e.g. '0x14a19...'. Get via get_pool_list."),
17
- positionId: z.string().refine((value) => POSITION_ID_RE.test(value), {
16
+ poolId: z.string().describe("Hex Pool ID, e.g. '0x14a19...'. Get via list_pools."),
17
+ positionId: z.string().optional().refine((value) => !value || POSITION_ID_RE.test(value), {
18
18
  message: "positionId must be empty string for new position, or a bytes32 hex string.",
19
- }).describe("Position ID: Use empty string '' for NEW positions, or valid hex for INCREASING existing ones. 0x000..00 is auto-treated as NEW."),
19
+ }).describe("Position ID: Use empty string '' for NEW positions, or valid hex for INCREASING existing ones. 0x000..00 is auto-treated as NEW. Default is empty string."),
20
20
  orderType: z.union([z.number(), z.string()]).describe("Market/Limit/Stop. e.g. 0 or 'MARKET'."),
21
21
  triggerType: z.union([z.number(), z.string()]).optional().describe("0=None (Market), 1=GTE, 2=LTE. e.g. 'GTE'."),
22
22
  direction: z.union([z.number(), z.string()]).describe("0/LONG/BUY or 1/SHORT/SELL."),
@@ -26,7 +26,7 @@ export const executeTradeTool = {
26
26
  timeInForce: z.coerce.number().int().describe("0=GTC, 1=IOC, 2=FOK"),
27
27
  postOnly: z.coerce.boolean().describe("If true, order only executes as Maker."),
28
28
  slippagePct: z.coerce.string().default("50").describe(`${SLIPPAGE_PCT_4DP_DESC}. Default is 50 (0.5%).`),
29
- executionFeeToken: z.string().describe("Address of token to pay gas/execution fees (typically USDC)."),
29
+ executionFeeToken: z.string().optional().describe("Address of token to pay gas/execution fees (typically USDC). Default is pool quoteToken."),
30
30
  leverage: z.coerce.number().positive().describe("Leverage multiplier, e.g., 10 for 10x."),
31
31
  tpSize: z.union([z.string(), z.number()]).optional().describe("Take Profit size. Use '0' to disable."),
32
32
  tpPrice: z.union([z.string(), z.number()]).optional().describe("Take Profit trigger price."),
@@ -35,7 +35,7 @@ export const executeTradeTool = {
35
35
  tradingFee: z.union([z.string(), z.number()]).optional().describe("Trading fee in quote token units. Supports human/raw prefix. Optional: auto-computed via get_user_trading_fee_rate."),
36
36
  assetClass: z.coerce.number().int().nonnegative().optional().describe("Optional fee lookup assetClass (default from pool config or 1)."),
37
37
  riskTier: z.coerce.number().int().nonnegative().optional().describe("Optional fee lookup riskTier (default from pool config or 1)."),
38
- marketId: z.string().describe("Specific Market Config Hash. Fetch via get_market_detail (resolve poolId first with search_market/get_pool_list)."),
38
+ marketId: z.string().describe("Specific Market Config Hash. Fetch via get_pool_metadata (resolve poolId first with find_pool/list_pools)."),
39
39
  },
40
40
  handler: async (args) => {
41
41
  try {
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ import { resolveClient } from "../auth/resolveClient.js";
3
+ import { searchMarket } from "../services/marketService.js";
4
+ export const findPoolTool = {
5
+ name: "find_pool",
6
+ description: "[MARKET] Search for pools by keyword or symbol. Returns matched market metadata.",
7
+ schema: {
8
+ keyword: z.string().describe("Search keyword or symbol, e.g. 'BTC', 'ETH/USDC'"),
9
+ limit: z.number().int().positive().optional().describe("Max search results (default 10)"),
10
+ },
11
+ handler: async (args) => {
12
+ try {
13
+ const { client } = await resolveClient();
14
+ const results = await searchMarket(client, args.keyword, args.limit ?? 10);
15
+ return {
16
+ content: [{
17
+ type: "text",
18
+ text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)
19
+ }]
20
+ };
21
+ }
22
+ catch (error) {
23
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
24
+ }
25
+ },
26
+ };
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
+ function unwrapData(result) {
4
+ if (result && typeof result === "object" && "data" in result) {
5
+ return result.data;
6
+ }
7
+ return result;
8
+ }
9
+ export const getAccountSnapshotTool = {
10
+ name: "get_account_snapshot",
11
+ description: "[ACCOUNT] Get unified account snapshot (balances, trading metrics, and VIP tier).",
12
+ schema: {
13
+ poolId: z.string().optional().describe("Pool ID for detailed trading-account metrics."),
14
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
15
+ },
16
+ handler: async (args) => {
17
+ try {
18
+ const { client, address } = await resolveClient();
19
+ const chainId = args.chainId ?? getChainId();
20
+ const { getBalances, getMarginBalance } = await import("../services/balanceService.js");
21
+ const [vipRes, walletRes] = await Promise.all([
22
+ client.account.getAccountVipInfo(chainId, address).catch(() => null),
23
+ getBalances(client, address, chainId).catch(() => null)
24
+ ]);
25
+ let tradingAccount = null;
26
+ if (args.poolId) {
27
+ const [info, margin] = await Promise.all([
28
+ client.account.getAccountInfo(chainId, address, args.poolId).catch(() => null),
29
+ getMarginBalance(client, address, args.poolId, chainId).catch(() => null)
30
+ ]);
31
+ tradingAccount = {
32
+ info: unwrapData(info),
33
+ margin: unwrapData(margin)
34
+ };
35
+ }
36
+ const results = {
37
+ wallet: unwrapData(walletRes),
38
+ tradingAccount,
39
+ vip: unwrapData(vipRes),
40
+ };
41
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
42
+ }
43
+ catch (error) {
44
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
45
+ }
46
+ },
47
+ };
@@ -1,7 +1,8 @@
1
1
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
2
+ import { getPoolList } from "../services/marketService.js";
2
3
  export const getAllTickersTool = {
3
4
  name: "get_all_tickers",
4
- description: "Get ticker snapshots for all markets.",
5
+ description: "[MARKET] Get ticker snapshots for all markets.",
5
6
  schema: {},
6
7
  handler: async () => {
7
8
  try {
@@ -13,9 +14,9 @@ export const getAllTickersTool = {
13
14
  catch {
14
15
  // Fallback for networks/environments where getAllTickers endpoint is unavailable.
15
16
  const chainId = getChainId();
16
- const poolList = await client.api.getPoolList();
17
- const pools = Array.isArray(poolList?.data) ? poolList.data : [];
18
- const poolIds = pools.map((p) => p.poolId).filter((id) => !!id);
17
+ const poolList = await getPoolList(client, chainId);
18
+ const pools = Array.isArray(poolList?.data) ? poolList.data : (Array.isArray(poolList) ? poolList : []);
19
+ const poolIds = pools.map((p) => p?.poolId ?? p?.pool_id).filter((id) => !!id);
19
20
  if (poolIds.length === 0) {
20
21
  throw new Error("Failed to fetch all tickers and no pools were available for fallback query.");
21
22
  }