@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.
- package/CHANGELOG.md +42 -6
- package/README.md +49 -414
- package/TOOL_EXAMPLES.md +63 -559
- package/dist/auth/resolveClient.js +19 -2
- package/dist/prompts/tradingGuide.js +16 -23
- package/dist/server.js +20 -2
- package/dist/services/balanceService.js +4 -4
- package/dist/services/marketService.js +48 -12
- package/dist/services/poolService.js +45 -44
- package/dist/services/tradeService.js +51 -23
- package/dist/tools/accountInfo.js +5 -134
- package/dist/tools/accountTransfer.js +7 -12
- package/dist/tools/adjustMargin.js +1 -1
- package/dist/tools/cancelOrders.js +71 -0
- package/dist/tools/checkAccountReady.js +75 -0
- package/dist/tools/checkApproval.js +1 -1
- package/dist/tools/closeAllPositions.js +9 -7
- package/dist/tools/closePosition.js +18 -11
- package/dist/tools/createPerpMarket.js +1 -1
- package/dist/tools/executeTrade.js +6 -6
- package/dist/tools/findPool.js +26 -0
- package/dist/tools/getAccountSnapshot.js +47 -0
- package/dist/tools/getAllTickers.js +5 -4
- package/dist/tools/getBaseDetail.js +1 -1
- package/dist/tools/getKline.js +7 -4
- package/dist/tools/getMyLpHoldings.js +3 -2
- package/dist/tools/getNetworkFee.js +1 -1
- package/dist/tools/getOrders.js +46 -0
- package/dist/tools/getPoolMetadata.js +95 -0
- package/dist/tools/getPositionsAll.js +80 -0
- package/dist/tools/{getMarketPrice.js → getPrice.js} +8 -5
- package/dist/tools/getUserTradingFeeRate.js +66 -3
- package/dist/tools/index.js +15 -19
- package/dist/tools/listPools.js +53 -0
- package/dist/tools/manageLiquidity.js +4 -4
- package/dist/tools/manageTpSl.js +234 -0
- package/dist/tools/openPositionSimple.js +27 -8
- package/dist/tools/searchTools.js +35 -0
- package/dist/utils/mappings.js +10 -7
- package/package.json +2 -2
- package/dist/tools/cancelAllOrders.js +0 -57
- package/dist/tools/cancelOrder.js +0 -22
- package/dist/tools/getAccountVipInfo.js +0 -20
- package/dist/tools/getKlineLatestBar.js +0 -28
- package/dist/tools/getOraclePrice.js +0 -22
- package/dist/tools/getPoolList.js +0 -17
- package/dist/tools/getPoolSymbolAll.js +0 -16
- package/dist/tools/getPositions.js +0 -77
- package/dist/tools/marketInfo.js +0 -88
- package/dist/tools/orderQueries.js +0 -51
- package/dist/tools/poolConfig.js +0 -22
- package/dist/tools/positionHistory.js +0 -28
- package/dist/tools/searchMarket.js +0 -21
- package/dist/tools/setTpSl.js +0 -50
- package/dist/tools/updateOrderTpSl.js +0 -34
|
@@ -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"),
|
package/dist/tools/getKline.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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,95 @@
|
|
|
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
|
+
function compactWarning(scope, err) {
|
|
21
|
+
const raw = extractErrorMessage(err);
|
|
22
|
+
const flat = raw.replace(/\s+/g, " ").trim();
|
|
23
|
+
const lower = flat.toLowerCase();
|
|
24
|
+
if (lower.includes("division or modulo by zero") || lower.includes("panic code 0x12")) {
|
|
25
|
+
return `${scope}: unavailable for this pool at current liquidity/price context.`;
|
|
26
|
+
}
|
|
27
|
+
if (flat.length > 220) {
|
|
28
|
+
return `${scope}: ${flat.slice(0, 220)}...`;
|
|
29
|
+
}
|
|
30
|
+
return `${scope}: ${flat}`;
|
|
31
|
+
}
|
|
32
|
+
export const getPoolMetadataTool = {
|
|
33
|
+
name: "get_pool_metadata",
|
|
34
|
+
description: "[MARKET] Get comprehensive metadata for a pool (market detail, on-chain info, liquidity, and limits).",
|
|
35
|
+
schema: {
|
|
36
|
+
poolId: z.string().optional().describe("Pool ID, Token Address, or Keyword"),
|
|
37
|
+
keyword: z.string().optional().describe("Market keyword (e.g. 'BTC')"),
|
|
38
|
+
includeLiquidity: z.boolean().default(false).describe("Whether to include liquidity depth (requires marketPrice)"),
|
|
39
|
+
marketPrice: z.union([z.string(), z.number()]).optional().describe("Current price (required if includeLiquidity is true)"),
|
|
40
|
+
includeConfig: z.boolean().default(false).describe("Whether to include pool level configuration/limits"),
|
|
41
|
+
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
42
|
+
},
|
|
43
|
+
handler: async (args) => {
|
|
44
|
+
try {
|
|
45
|
+
const { client } = await resolveClient();
|
|
46
|
+
const chainId = args.chainId ?? getChainId();
|
|
47
|
+
const poolId = await resolvePool(client, args.poolId, args.keyword, chainId);
|
|
48
|
+
const results = { poolId, chainId };
|
|
49
|
+
const errors = [];
|
|
50
|
+
// 1. Market Detail (Fee rates, OI, etc.)
|
|
51
|
+
try {
|
|
52
|
+
results.marketDetail = await getMarketDetail(client, poolId, chainId);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
errors.push(compactWarning("marketDetail", err));
|
|
56
|
+
}
|
|
57
|
+
// 2. Pool Info (Reserves, Utilization)
|
|
58
|
+
try {
|
|
59
|
+
results.poolInfo = await getPoolInfo(poolId, chainId, client);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
errors.push(compactWarning("poolInfo", err));
|
|
63
|
+
}
|
|
64
|
+
// 3. Optional: Liquidity Info
|
|
65
|
+
if (args.includeLiquidity) {
|
|
66
|
+
try {
|
|
67
|
+
const price = normalizeMarketPrice30Input(args.marketPrice);
|
|
68
|
+
if (!price)
|
|
69
|
+
throw new Error("marketPrice is required for liquidity info.");
|
|
70
|
+
results.liquidityInfo = await getLiquidityInfo(client, poolId, price, chainId);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
errors.push(compactWarning("liquidityInfo", err));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// 4. Optional: Config / Limits
|
|
77
|
+
if (args.includeConfig) {
|
|
78
|
+
try {
|
|
79
|
+
const { getPoolLevelConfig } = await import("../services/marketService.js");
|
|
80
|
+
results.levelConfig = await getPoolLevelConfig(client, poolId, chainId);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
errors.push(compactWarning("levelConfig", err));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (errors.length > 0) {
|
|
87
|
+
results.warnings = errors;
|
|
88
|
+
}
|
|
89
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return { content: [{ type: "text", text: `Error: ${extractErrorMessage(error)}` }], isError: true };
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
};
|
|
@@ -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
|
|
5
|
-
name: "
|
|
6
|
-
description: "Get the current
|
|
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 =
|
|
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) {
|
|
@@ -1,8 +1,65 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
+
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
4
|
+
function compactMessage(message) {
|
|
5
|
+
const flat = String(message ?? "").replace(/\s+/g, " ").trim();
|
|
6
|
+
if (!flat)
|
|
7
|
+
return "Unknown fee-rate read error.";
|
|
8
|
+
if (flat.length <= 240)
|
|
9
|
+
return flat;
|
|
10
|
+
return `${flat.slice(0, 240)}...`;
|
|
11
|
+
}
|
|
12
|
+
async function withMutedSdkFeeRateLogs(runner) {
|
|
13
|
+
const original = console.error;
|
|
14
|
+
console.error = (...args) => {
|
|
15
|
+
const first = args?.[0];
|
|
16
|
+
const firstText = typeof first === "string"
|
|
17
|
+
? first
|
|
18
|
+
: first instanceof Error
|
|
19
|
+
? first.message
|
|
20
|
+
: String(first ?? "");
|
|
21
|
+
const lower = firstText.toLowerCase();
|
|
22
|
+
if (lower.includes("myx-sdk-error") ||
|
|
23
|
+
(lower.includes("getuserfeerate") && (lower.includes("revert") || lower.includes("contractfunctionexecutionerror")))) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
original(...args);
|
|
27
|
+
};
|
|
28
|
+
try {
|
|
29
|
+
return await runner();
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
console.error = original;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function buildErrorPayload(args, messageLike) {
|
|
36
|
+
const message = compactMessage(extractErrorMessage(messageLike));
|
|
37
|
+
const lower = message.toLowerCase();
|
|
38
|
+
const code = lower.includes("invalidparameter") || lower.includes("invalid parameter")
|
|
39
|
+
? "INVALID_PARAM"
|
|
40
|
+
: "SDK_READ_ERROR";
|
|
41
|
+
const hint = code === "INVALID_PARAM"
|
|
42
|
+
? "Check assetClass/riskTier for this pool and retry."
|
|
43
|
+
: "Fee tier may be unavailable for current account/market context. Retry with valid assetClass/riskTier or provide tradingFee manually.";
|
|
44
|
+
return {
|
|
45
|
+
status: "error",
|
|
46
|
+
error: {
|
|
47
|
+
tool: "get_user_trading_fee_rate",
|
|
48
|
+
code,
|
|
49
|
+
message,
|
|
50
|
+
hint,
|
|
51
|
+
action: "Adjust params/context and retry.",
|
|
52
|
+
details: {
|
|
53
|
+
assetClass: args.assetClass,
|
|
54
|
+
riskTier: args.riskTier,
|
|
55
|
+
chainId: args.chainId ?? getChainId(),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
3
60
|
export const getUserTradingFeeRateTool = {
|
|
4
61
|
name: "get_user_trading_fee_rate",
|
|
5
|
-
description: "Get maker/taker fee rates for a given assetClass and riskTier.",
|
|
62
|
+
description: "[TRADE] Get maker/taker fee rates for a given assetClass and riskTier.",
|
|
6
63
|
schema: {
|
|
7
64
|
assetClass: z.coerce.number().int().nonnegative().describe("Asset class ID"),
|
|
8
65
|
riskTier: z.coerce.number().int().nonnegative().describe("Risk tier"),
|
|
@@ -12,11 +69,17 @@ export const getUserTradingFeeRateTool = {
|
|
|
12
69
|
try {
|
|
13
70
|
const { client } = await resolveClient();
|
|
14
71
|
const chainId = args.chainId ?? getChainId();
|
|
15
|
-
const result = await client.utils.getUserTradingFeeRate(args.assetClass, args.riskTier, chainId);
|
|
72
|
+
const result = await withMutedSdkFeeRateLogs(() => client.utils.getUserTradingFeeRate(args.assetClass, args.riskTier, chainId));
|
|
73
|
+
const maybeCode = Number(result?.code);
|
|
74
|
+
if (Number.isFinite(maybeCode) && maybeCode !== 0) {
|
|
75
|
+
const body = buildErrorPayload(args, result?.msg ?? result?.message ?? result);
|
|
76
|
+
return { content: [{ type: "text", text: JSON.stringify(body, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }], isError: true };
|
|
77
|
+
}
|
|
16
78
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
|
|
17
79
|
}
|
|
18
80
|
catch (error) {
|
|
19
|
-
|
|
81
|
+
const body = buildErrorPayload(args, error);
|
|
82
|
+
return { content: [{ type: "text", text: JSON.stringify(body, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }], isError: true };
|
|
20
83
|
}
|
|
21
84
|
},
|
|
22
85
|
};
|
package/dist/tools/index.js
CHANGED
|
@@ -1,36 +1,32 @@
|
|
|
1
1
|
// Tools — 交易
|
|
2
2
|
export { openPositionSimpleTool } from "./openPositionSimple.js";
|
|
3
3
|
export { executeTradeTool } from "./executeTrade.js";
|
|
4
|
-
export {
|
|
5
|
-
export { cancelAllOrdersTool } from "./cancelAllOrders.js";
|
|
4
|
+
export { cancelOrdersTool } from "./cancelOrders.js"; // Unified
|
|
6
5
|
export { closePositionTool } from "./closePosition.js";
|
|
7
|
-
export {
|
|
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 {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
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 {
|
|
31
|
-
export {
|
|
32
|
-
export {
|
|
33
|
-
export {
|
|
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";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { resolveClient } from "../auth/resolveClient.js";
|
|
2
|
+
import { getPoolList } from "../services/marketService.js";
|
|
3
|
+
function collectRows(input) {
|
|
4
|
+
if (Array.isArray(input))
|
|
5
|
+
return input.flatMap(collectRows);
|
|
6
|
+
if (!input || typeof input !== "object")
|
|
7
|
+
return [];
|
|
8
|
+
if (input.poolId || input.pool_id)
|
|
9
|
+
return [input];
|
|
10
|
+
return Object.values(input).flatMap(collectRows);
|
|
11
|
+
}
|
|
12
|
+
export const listPoolsTool = {
|
|
13
|
+
name: "list_pools",
|
|
14
|
+
description: "[MARKET] Get the complete list of all available tradable pools, including symbol and icon metadata.",
|
|
15
|
+
schema: {},
|
|
16
|
+
handler: async () => {
|
|
17
|
+
try {
|
|
18
|
+
const { client } = await resolveClient();
|
|
19
|
+
// Fetch both list and symbols
|
|
20
|
+
const [poolListRes, symbolsRes] = await Promise.all([
|
|
21
|
+
getPoolList(client),
|
|
22
|
+
client.markets.getPoolSymbolAll().catch(() => ({ data: [] }))
|
|
23
|
+
]);
|
|
24
|
+
const poolsRaw = collectRows(poolListRes?.data ?? poolListRes);
|
|
25
|
+
const symbolsRaw = collectRows(symbolsRes?.data ?? symbolsRes);
|
|
26
|
+
const symbolMap = new Map(symbolsRaw
|
|
27
|
+
.filter((row) => row?.poolId || row?.pool_id)
|
|
28
|
+
.map((s) => [String(s.poolId ?? s.pool_id).toLowerCase(), s]));
|
|
29
|
+
const deduped = new Map();
|
|
30
|
+
for (const row of poolsRaw) {
|
|
31
|
+
const poolId = String(row?.poolId ?? row?.pool_id ?? "").trim().toLowerCase();
|
|
32
|
+
if (!poolId)
|
|
33
|
+
continue;
|
|
34
|
+
if (!deduped.has(poolId)) {
|
|
35
|
+
deduped.set(poolId, row);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const enriched = Array.from(deduped.values()).map(pool => {
|
|
39
|
+
const poolId = String(pool.poolId ?? pool.pool_id ?? "").toLowerCase();
|
|
40
|
+
const symbolData = symbolMap.get(poolId);
|
|
41
|
+
return {
|
|
42
|
+
...pool,
|
|
43
|
+
icon: symbolData?.icon || null,
|
|
44
|
+
symbolName: symbolData?.symbolName || pool.symbolName || pool.baseQuoteSymbol || pool.symbol || null,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: enriched }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -47,7 +47,7 @@ function resolveLpAssetNames(detail) {
|
|
|
47
47
|
}
|
|
48
48
|
export const manageLiquidityTool = {
|
|
49
49
|
name: "manage_liquidity",
|
|
50
|
-
description: "Add or withdraw liquidity from a BASE or QUOTE pool. Success response includes LP naming metadata: base `mBASE.QUOTE`, quote `mQUOTE.BASE`, plus `operatedLpAssetName` based on poolType.",
|
|
50
|
+
description: "[LIQUIDITY] Add or withdraw liquidity from a BASE or QUOTE pool. Success response includes LP naming metadata: base `mBASE.QUOTE`, quote `mQUOTE.BASE`, plus `operatedLpAssetName` based on poolType.",
|
|
51
51
|
schema: {
|
|
52
52
|
action: z.coerce.string().describe("'deposit' or 'withdraw' (aliases: add/remove/increase/decrease; case-insensitive)"),
|
|
53
53
|
poolType: z.enum(["BASE", "QUOTE"]).describe("'BASE' or 'QUOTE'"),
|
|
@@ -79,7 +79,7 @@ export const manageLiquidityTool = {
|
|
|
79
79
|
const detail = detailRes?.data || (detailRes?.marketId ? detailRes : null);
|
|
80
80
|
if (!detail?.marketId) {
|
|
81
81
|
throw new Error(`Pool ${poolId} not found on chainId ${chainId}. ` +
|
|
82
|
-
`Please query a valid active pool via
|
|
82
|
+
`Please query a valid active pool via find_pool/list_pools first.`);
|
|
83
83
|
}
|
|
84
84
|
let raw;
|
|
85
85
|
if (poolType === "QUOTE") {
|
|
@@ -93,7 +93,7 @@ export const manageLiquidityTool = {
|
|
|
93
93
|
: await baseWithdraw(poolId, amount, slippage, chainId);
|
|
94
94
|
}
|
|
95
95
|
if (!raw) {
|
|
96
|
-
throw new Error(`SDK returned an empty result for liquidity ${action}. This usually occurs if the pool is not in an Active state (state: 2) or if there is a contract-level restriction. Please check
|
|
96
|
+
throw new Error(`SDK returned an empty result for liquidity ${action}. This usually occurs if the pool is not in an Active state (state: 2) or if there is a contract-level restriction. Please check get_pool_metadata.`);
|
|
97
97
|
}
|
|
98
98
|
if (raw && typeof raw === "object" && "code" in raw && Number(raw.code) !== 0) {
|
|
99
99
|
throw new Error(`Liquidity ${action} failed: ${extractErrorMessage(raw)}`);
|
|
@@ -127,7 +127,7 @@ export const manageLiquidityTool = {
|
|
|
127
127
|
};
|
|
128
128
|
export const getLpPriceTool = {
|
|
129
129
|
name: "get_lp_price",
|
|
130
|
-
description: "Get the current internal net asset value (NAV) price of an LP token for a BASE or QUOTE pool. Note: This is NOT the underlying token's external Oracle market price (e.g. WETH's price), but rather the internal exchange rate / net worth of the LP token itself which fluctuates based on pool PnL and fees.",
|
|
130
|
+
description: "[LIQUIDITY] Get the current internal net asset value (NAV) price of an LP token for a BASE or QUOTE pool. Note: This is NOT the underlying token's external Oracle market price (e.g. WETH's price), but rather the internal exchange rate / net worth of the LP token itself which fluctuates based on pool PnL and fees.",
|
|
131
131
|
schema: {
|
|
132
132
|
poolType: z.enum(["BASE", "QUOTE"]).describe("'BASE' or 'QUOTE'"),
|
|
133
133
|
poolId: z.string().describe("Pool ID"),
|