@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
@@ -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
@@ -24,7 +24,7 @@ function readRawPositionField(position, primary, fallback) {
24
24
  }
25
25
  export const closePositionTool = {
26
26
  name: "close_position",
27
- description: "Create a decrease order using SDK-native parameters.",
27
+ description: "[TRADE] Create a decrease order (close or reduce position) using SDK-native parameters.",
28
28
  schema: {
29
29
  poolId: z.string().describe("Pool ID"),
30
30
  positionId: z.string().describe("Position ID to close"),
@@ -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,41 @@
1
+ import { z } from "zod";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
+ export const getAccountSnapshotTool = {
4
+ name: "get_account_snapshot",
5
+ description: "[ACCOUNT] Get unified account snapshot (balances, trading metrics, and VIP tier).",
6
+ schema: {
7
+ poolId: z.string().optional().describe("Pool ID for detailed trading-account metrics."),
8
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
9
+ },
10
+ handler: async (args) => {
11
+ try {
12
+ const { client, address } = await resolveClient();
13
+ const chainId = args.chainId ?? getChainId();
14
+ const { getBalances, getMarginBalance } = await import("../services/balanceService.js");
15
+ const [vipRes, walletRes] = await Promise.all([
16
+ client.account.getAccountVipInfo(chainId, address).catch(() => null),
17
+ getBalances(client, address, chainId).catch(() => null)
18
+ ]);
19
+ let tradingAccount = null;
20
+ if (args.poolId) {
21
+ const [info, margin] = await Promise.all([
22
+ client.account.getAccountInfo(chainId, address, args.poolId).catch(() => null),
23
+ getMarginBalance(client, address, args.poolId, chainId).catch(() => null)
24
+ ]);
25
+ tradingAccount = {
26
+ info: info?.data || info,
27
+ margin: margin?.data || margin
28
+ };
29
+ }
30
+ const results = {
31
+ wallet: walletRes?.data || walletRes,
32
+ tradingAccount,
33
+ vip: vipRes?.data || vipRes,
34
+ };
35
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
36
+ }
37
+ catch (error) {
38
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
39
+ }
40
+ },
41
+ };
@@ -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
  }
@@ -2,7 +2,7 @@ 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 token details.",
5
+ description: "[MARKET] Get base token details.",
6
6
  schema: {
7
7
  baseAddress: z.string().describe("Base token address"),
8
8
  chainId: z.number().int().positive().optional().describe("Optional chainId override"),
@@ -3,18 +3,21 @@ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  const klineIntervalSchema = z.enum(["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "1M"]);
4
4
  export const getKlineTool = {
5
5
  name: "get_kline",
6
- description: "Get K-line / candlestick data for a pool. Essential for price analysis.",
6
+ description: "[MARKET] Get K-line / candlestick data for a pool. Supports PoolId or Keyword. Set limit=1 for the latest bar.",
7
7
  schema: {
8
- poolId: z.string().describe("Pool ID"),
8
+ poolId: z.string().optional().describe("Pool ID"),
9
+ keyword: z.string().optional().describe("Market keyword (e.g. 'BTC')"),
9
10
  interval: klineIntervalSchema.describe("K-line interval"),
10
- limit: z.number().int().positive().optional().describe("Number of bars (default 100)"),
11
+ limit: z.number().int().positive().optional().describe("Number of bars (default 100). Set to 1 for the latest price bar."),
11
12
  },
12
13
  handler: async (args) => {
13
14
  try {
14
15
  const { client } = await resolveClient();
15
16
  const chainId = getChainId();
17
+ const { resolvePool } = await import("../services/marketService.js");
18
+ const poolId = await resolvePool(client, args.poolId, args.keyword);
16
19
  const result = await client.markets.getKlineList({
17
- poolId: args.poolId,
20
+ poolId,
18
21
  chainId,
19
22
  interval: args.interval,
20
23
  limit: args.limit ?? 100,
@@ -3,6 +3,7 @@ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  import { COMMON_LP_AMOUNT_DECIMALS } from "@myx-trade/sdk";
4
4
  import { Contract, formatUnits } from "ethers";
5
5
  import { extractErrorMessage } from "../utils/errorMessage.js";
6
+ import { getPoolList } from "../services/marketService.js";
6
7
  const ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
7
8
  const ERC20_BALANCE_ABI = ["function balanceOf(address owner) view returns (uint256)"];
8
9
  function collectRows(input) {
@@ -130,7 +131,7 @@ async function readErc20Balance(provider, tokenAddress, holder) {
130
131
  }
131
132
  export const getMyLpHoldingsTool = {
132
133
  name: "get_my_lp_holdings",
133
- description: "List your LP holdings across pools on the current chain by reading base/quote LP token balances. Includes standardized LP asset names: base LP `mBASE.QUOTE`, quote LP `mQUOTE.BASE`.",
134
+ description: "[ACCOUNT] List your LP holdings across pools on the current chain by reading base/quote LP token balances. Includes standardized LP asset names: base LP `mBASE.QUOTE`, quote LP `mQUOTE.BASE`.",
134
135
  schema: {
135
136
  includeZero: z.coerce.boolean().optional().describe("If true, include pools with zero LP balances (default false)."),
136
137
  poolIds: z.union([z.array(z.string()).min(1), z.string().min(1)]).optional().describe("Optional poolId filter. Supports array, JSON-array string, comma string, or single poolId."),
@@ -148,7 +149,7 @@ export const getMyLpHoldingsTool = {
148
149
  const poolIdsFilter = normalizePoolIdsInput(args.poolIds);
149
150
  const filterSet = new Set(poolIdsFilter.map((item) => item.toLowerCase()));
150
151
  const maxPools = Number.isFinite(Number(args.maxPools)) ? Math.floor(Number(args.maxPools)) : null;
151
- const poolListRes = await client.api.getPoolList();
152
+ const poolListRes = await getPoolList(client, chainId);
152
153
  const rows = collectRows(poolListRes?.data ?? poolListRes);
153
154
  const deduped = new Map();
154
155
  for (const row of rows) {
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  export const getNetworkFeeTool = {
4
4
  name: "get_network_fee",
5
- description: "Estimate network fee requirements for a market.",
5
+ description: "[TRADE] Estimate network fee requirements for a market.",
6
6
  schema: {
7
7
  marketId: z.string().describe("Market ID"),
8
8
  chainId: z.number().int().positive().optional().describe("Optional chainId override"),
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
+ import { getOrderTypeDesc, getOrderStatusDesc, getDirectionDesc, getHistoryOrderStatusDesc, getExecTypeDesc } from "../utils/mappings.js";
4
+ export const getOrdersTool = {
5
+ name: "get_orders",
6
+ description: "[ACCOUNT] Get orders (open or history) with optional filters.",
7
+ schema: {
8
+ status: z.enum(["OPEN", "HISTORY", "ALL"]).default("OPEN").describe("Filter by status: 'OPEN' (default), 'HISTORY', or 'ALL'"),
9
+ poolId: z.string().optional().describe("Filter by pool ID"),
10
+ limit: z.number().int().positive().optional().describe("Results per page (default 20)"),
11
+ },
12
+ handler: async (args) => {
13
+ try {
14
+ const { client, address } = await resolveClient();
15
+ const chainId = getChainId();
16
+ const results = {};
17
+ if (args.status === "OPEN" || args.status === "ALL") {
18
+ const openRes = await client.order.getOrders(address);
19
+ results.open = (openRes?.data || []).map((order) => ({
20
+ ...order,
21
+ orderTypeDesc: getOrderTypeDesc(order.orderType),
22
+ statusDesc: getOrderStatusDesc(order.status),
23
+ directionDesc: getDirectionDesc(order.direction)
24
+ }));
25
+ if (args.poolId) {
26
+ results.open = results.open.filter((o) => String(o.poolId).toLowerCase() === args.poolId.toLowerCase());
27
+ }
28
+ }
29
+ if (args.status === "HISTORY" || args.status === "ALL") {
30
+ const query = { chainId, poolId: args.poolId, limit: args.limit ?? 20 };
31
+ const historyRes = await client.order.getOrderHistory(query, address);
32
+ results.history = (historyRes?.data || []).map((order) => ({
33
+ ...order,
34
+ orderTypeDesc: getOrderTypeDesc(order.orderType),
35
+ orderStatusDesc: getHistoryOrderStatusDesc(order.orderStatus),
36
+ directionDesc: getDirectionDesc(order.direction),
37
+ execTypeDesc: getExecTypeDesc(order.execType)
38
+ }));
39
+ }
40
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
41
+ }
42
+ catch (error) {
43
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
44
+ }
45
+ },
46
+ };
@@ -0,0 +1,83 @@
1
+ import { z } from "zod";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
+ import { getMarketDetail, resolvePool } from "../services/marketService.js";
4
+ import { getPoolInfo, getLiquidityInfo } from "../services/poolService.js";
5
+ import { extractErrorMessage } from "../utils/errorMessage.js";
6
+ import { parseUserPrice30 } from "../utils/units.js";
7
+ const INTEGER_RE = /^\d+$/;
8
+ const DECIMAL_RE = /^\d+(\.\d+)?$/;
9
+ function normalizeMarketPrice30Input(value) {
10
+ const text = String(value ?? "").trim();
11
+ if (!text)
12
+ return "";
13
+ if (INTEGER_RE.test(text))
14
+ return text;
15
+ if (!DECIMAL_RE.test(text) && !/^raw:/i.test(text) && !/^human:/i.test(text)) {
16
+ throw new Error("marketPrice must be numeric, or prefixed with raw:/human:.");
17
+ }
18
+ return parseUserPrice30(text, "marketPrice");
19
+ }
20
+ export const getPoolMetadataTool = {
21
+ name: "get_pool_metadata",
22
+ description: "[MARKET] Get comprehensive metadata for a pool (market detail, on-chain info, liquidity, and limits).",
23
+ schema: {
24
+ poolId: z.string().optional().describe("Pool ID, Token Address, or Keyword"),
25
+ keyword: z.string().optional().describe("Market keyword (e.g. 'BTC')"),
26
+ includeLiquidity: z.boolean().default(false).describe("Whether to include liquidity depth (requires marketPrice)"),
27
+ marketPrice: z.union([z.string(), z.number()]).optional().describe("Current price (required if includeLiquidity is true)"),
28
+ includeConfig: z.boolean().default(false).describe("Whether to include pool level configuration/limits"),
29
+ chainId: z.number().int().positive().optional().describe("Optional chainId override"),
30
+ },
31
+ handler: async (args) => {
32
+ try {
33
+ const { client } = await resolveClient();
34
+ const chainId = args.chainId ?? getChainId();
35
+ const poolId = await resolvePool(client, args.poolId, args.keyword, chainId);
36
+ const results = { poolId, chainId };
37
+ const errors = [];
38
+ // 1. Market Detail (Fee rates, OI, etc.)
39
+ try {
40
+ results.marketDetail = await getMarketDetail(client, poolId, chainId);
41
+ }
42
+ catch (err) {
43
+ errors.push(`marketDetail: ${extractErrorMessage(err)}`);
44
+ }
45
+ // 2. Pool Info (Reserves, Utilization)
46
+ try {
47
+ results.poolInfo = await getPoolInfo(poolId, chainId, client);
48
+ }
49
+ catch (err) {
50
+ errors.push(`poolInfo: ${extractErrorMessage(err)}`);
51
+ }
52
+ // 3. Optional: Liquidity Info
53
+ if (args.includeLiquidity) {
54
+ try {
55
+ const price = normalizeMarketPrice30Input(args.marketPrice);
56
+ if (!price)
57
+ throw new Error("marketPrice is required for liquidity info.");
58
+ results.liquidityInfo = await getLiquidityInfo(client, poolId, price, chainId);
59
+ }
60
+ catch (err) {
61
+ errors.push(`liquidityInfo: ${extractErrorMessage(err)}`);
62
+ }
63
+ }
64
+ // 4. Optional: Config / Limits
65
+ if (args.includeConfig) {
66
+ try {
67
+ const { getPoolLevelConfig } = await import("../services/marketService.js");
68
+ results.levelConfig = await getPoolLevelConfig(client, poolId, chainId);
69
+ }
70
+ catch (err) {
71
+ errors.push(`levelConfig: ${extractErrorMessage(err)}`);
72
+ }
73
+ }
74
+ if (errors.length > 0) {
75
+ results.warnings = errors;
76
+ }
77
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
78
+ }
79
+ catch (error) {
80
+ return { content: [{ type: "text", text: `Error: ${extractErrorMessage(error)}` }], isError: true };
81
+ }
82
+ },
83
+ };
@@ -0,0 +1,80 @@
1
+ import { z } from "zod";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
+ import { getPositions } from "../services/positionService.js";
4
+ import { getDirectionDesc } from "../utils/mappings.js";
5
+ export const getPositionsAllTool = {
6
+ name: "get_positions_all",
7
+ description: "[ACCOUNT] Get positions (open or history) with optional filters and ROI/PnL metrics.",
8
+ schema: {
9
+ status: z.enum(["OPEN", "HISTORY", "ALL"]).default("OPEN").describe("Filter by status: 'OPEN' (default), 'HISTORY', or 'ALL'"),
10
+ poolId: z.string().optional().describe("Filter by pool ID"),
11
+ limit: z.number().int().positive().optional().describe("Results per page (default 20, for history)"),
12
+ },
13
+ handler: async (args) => {
14
+ try {
15
+ const { client, address } = await resolveClient();
16
+ const chainId = getChainId();
17
+ const results = {};
18
+ if (args.status === "OPEN" || args.status === "ALL") {
19
+ const data = await getPositions(client, address);
20
+ const positions = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
21
+ // Enhance open positions with metrics
22
+ const filtered = args.poolId ? positions.filter((p) => String(p.poolId).toLowerCase() === args.poolId.toLowerCase()) : positions;
23
+ if (filtered.length > 0) {
24
+ const poolIds = [...new Set(filtered.map((p) => p.poolId))];
25
+ const [tickersRes, configs] = await Promise.all([
26
+ client.markets.getTickerList({ chainId, poolIds }),
27
+ Promise.all(poolIds.map(async (pid) => {
28
+ try {
29
+ const { getPoolLevelConfig } = await import("../services/marketService.js");
30
+ const res = await getPoolLevelConfig(client, pid, chainId);
31
+ return { poolId: pid, config: res?.levelConfig || res?.data?.levelConfig || res };
32
+ }
33
+ catch {
34
+ return { poolId: pid, config: null };
35
+ }
36
+ }))
37
+ ]);
38
+ const tickers = Array.isArray(tickersRes) ? tickersRes : (tickersRes?.data ?? []);
39
+ results.open = filtered.map((pos) => {
40
+ const ticker = tickers.find((t) => t.poolId === pos.poolId);
41
+ const currentPrice = Number(ticker?.price || 0);
42
+ const entryPrice = Number(pos.entryPrice || 0);
43
+ const size = Number(pos.size || 0);
44
+ const collateral = Number(pos.collateralAmount || 0);
45
+ const direction = pos.direction;
46
+ const mm = configs.find(c => c.poolId === pos.poolId)?.config?.maintainCollateralRate || 0.02;
47
+ let estimatedPnl = direction === 0 ? (currentPrice - entryPrice) * size : (entryPrice - currentPrice) * size;
48
+ const roi = collateral > 0 ? (estimatedPnl / collateral) * 100 : 0;
49
+ let liqPrice = size > 0 ? (direction === 0 ? (entryPrice * size - collateral) / (size * (1 - mm)) : (entryPrice * size + collateral) / (size * (1 + mm))) : 0;
50
+ if (liqPrice < 0)
51
+ liqPrice = 0;
52
+ return {
53
+ ...pos,
54
+ directionDesc: getDirectionDesc(pos.direction),
55
+ currentPrice: currentPrice.toString(),
56
+ estimatedPnl: estimatedPnl.toFixed(4),
57
+ roi: roi.toFixed(2) + "%",
58
+ liquidationPrice: liqPrice.toFixed(4)
59
+ };
60
+ });
61
+ }
62
+ else {
63
+ results.open = [];
64
+ }
65
+ }
66
+ if (args.status === "HISTORY" || args.status === "ALL") {
67
+ const query = { chainId, poolId: args.poolId, limit: args.limit ?? 20 };
68
+ const historyRes = await client.position.getPositionHistory(query, address);
69
+ results.history = (historyRes?.data || []).map((pos) => ({
70
+ ...pos,
71
+ directionDesc: getDirectionDesc(pos.direction)
72
+ }));
73
+ }
74
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
75
+ }
76
+ catch (error) {
77
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
78
+ }
79
+ },
80
+ };
@@ -1,18 +1,21 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
- import { getMarketPrice } from "../services/marketService.js";
4
- export const getMarketPriceTool = {
5
- name: "get_market_price",
6
- description: "Get the current market price for a specific pool.",
3
+ import { getMarketPrice, getOraclePrice } from "../services/marketService.js";
4
+ export const getPriceTool = {
5
+ name: "get_price",
6
+ description: "[MARKET] Get the current price for a specific pool. Support both market (impact) and oracle prices.",
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID to get price for"),
9
+ priceType: z.enum(["market", "oracle"]).default("market").describe("Type of price to fetch: 'market' (default) or 'oracle'"),
9
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
15
  const chainId = args.chainId ?? getChainId();
15
- const data = await getMarketPrice(client, args.poolId, chainId);
16
+ const data = args.priceType === "oracle"
17
+ ? await getOraclePrice(client, args.poolId, chainId)
18
+ : await getMarketPrice(client, args.poolId, chainId);
16
19
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
17
20
  }
18
21
  catch (error) {
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  export const getUserTradingFeeRateTool = {
4
4
  name: "get_user_trading_fee_rate",
5
- description: "Get maker/taker fee rates for a given assetClass and riskTier.",
5
+ description: "[TRADE] Get maker/taker fee rates for a given assetClass and riskTier.",
6
6
  schema: {
7
7
  assetClass: z.coerce.number().int().nonnegative().describe("Asset class ID"),
8
8
  riskTier: z.coerce.number().int().nonnegative().describe("Risk tier"),
@@ -1,36 +1,32 @@
1
1
  // Tools — 交易
2
2
  export { openPositionSimpleTool } from "./openPositionSimple.js";
3
3
  export { executeTradeTool } from "./executeTrade.js";
4
- export { cancelOrderTool } from "./cancelOrder.js";
5
- export { cancelAllOrdersTool } from "./cancelAllOrders.js";
4
+ export { cancelOrdersTool } from "./cancelOrders.js"; // Unified
6
5
  export { closePositionTool } from "./closePosition.js";
7
- export { setTpSlTool } from "./setTpSl.js";
8
- export { updateOrderTpSlTool } from "./updateOrderTpSl.js";
6
+ export { manageTpSlTool } from "./manageTpSl.js"; // Unified
9
7
  export { adjustMarginTool } from "./adjustMargin.js";
10
8
  export { closeAllPositionsTool } from "./closeAllPositions.js";
11
9
  export { checkApprovalTool } from "./checkApproval.js";
12
10
  export { getUserTradingFeeRateTool } from "./getUserTradingFeeRate.js";
13
11
  export { getNetworkFeeTool } from "./getNetworkFee.js";
12
+ export { checkAccountReadyTool } from "./checkAccountReady.js";
14
13
  // Tools — 市场数据
15
- export { getMarketPriceTool } from "./getMarketPrice.js";
16
- export { getOraclePriceTool } from "./getOraclePrice.js";
17
- export { searchMarketTool } from "./searchMarket.js";
18
- export { getKlineTool } from "./getKline.js";
19
- export { getKlineLatestBarTool } from "./getKlineLatestBar.js";
20
- export { getAllTickersTool } from "./getAllTickers.js";
21
- export { getMarketDetailTool, getPoolInfoTool, getLiquidityInfoTool } from "./marketInfo.js";
22
- export { getPoolListTool } from "./getPoolList.js";
23
- export { getPoolSymbolAllTool } from "./getPoolSymbolAll.js";
24
- export { getPoolLevelConfigTool } from "./poolConfig.js";
14
+ export { getPriceTool } from "./getPrice.js"; // Unified
15
+ export { getKlineTool } from "./getKline.js"; // Enhanced
16
+ export { listPoolsTool } from "./listPools.js"; // Unified
17
+ export { findPoolTool } from "./findPool.js"; // Unified
18
+ export { getPoolMetadataTool } from "./getPoolMetadata.js"; // Unified
25
19
  export { getBaseDetailTool } from "./getBaseDetail.js";
20
+ export { getAllTickersTool } from "./getAllTickers.js";
26
21
  // Tools — 池子 & 流动性
27
22
  export { createPerpMarketTool } from "./createPerpMarket.js";
28
23
  export { manageLiquidityTool, getLpPriceTool } from "./manageLiquidity.js";
29
24
  // Tools — 账户 & 查询
30
- export { getPositionsTool } from "./getPositions.js";
31
- export { getOpenOrdersTool, getOrderHistoryTool } from "./orderQueries.js";
32
- export { getPositionHistoryTool } from "./positionHistory.js";
33
- export { getAccountTool, getTradeFlowTool } from "./accountInfo.js";
25
+ export { getPositionsAllTool } from "./getPositionsAll.js"; // Unified
26
+ export { getOrdersTool } from "./getOrders.js"; // Unified
27
+ export { getAccountSnapshotTool } from "./getAccountSnapshot.js"; // Unified
28
+ export { getTradeFlowTool } from "./accountInfo.js"; // Kept for trade flow detail
34
29
  export { getMyLpHoldingsTool } from "./getMyLpHoldings.js";
35
- export { getAccountVipInfoTool } from "./getAccountVipInfo.js";
36
30
  export { accountDepositTool, accountWithdrawTool } from "./accountTransfer.js";
31
+ // Tools — 系统
32
+ export { searchToolsTool } from "./searchTools.js";