@michaleffffff/mcp-trading-server 2.3.6 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.js +2 -2
- package/dist/services/marketService.js +8 -8
- package/dist/services/poolService.js +16 -16
- package/dist/services/tradeService.js +17 -16
- package/dist/tools/accountInfo.js +2 -1
- package/dist/tools/adjustMargin.js +1 -1
- package/dist/tools/cancelOrder.js +4 -3
- package/dist/tools/closeAllPositions.js +2 -1
- package/dist/tools/closePosition.js +4 -3
- package/dist/tools/executeTrade.js +6 -5
- package/dist/tools/getBaseDetail.js +57 -3
- package/dist/tools/getMarketPrice.js +4 -2
- package/dist/tools/getOraclePrice.js +4 -2
- package/dist/tools/manageLiquidity.js +11 -8
- package/dist/tools/marketInfo.js +10 -4
- package/dist/tools/poolConfig.js +4 -2
- package/dist/tools/setTpSl.js +6 -4
- package/dist/tools/updateOrderTpSl.js +7 -4
- package/dist/utils/schema.js +66 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -81,7 +81,7 @@ function zodSchemaToJsonSchema(zodSchema) {
|
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
// ─── MCP Server ───
|
|
84
|
-
const server = new Server({ name: "myx-mcp-trading-server", version: "2.
|
|
84
|
+
const server = new Server({ name: "myx-mcp-trading-server", version: "2.4.0" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
|
|
85
85
|
// List tools
|
|
86
86
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
87
87
|
return {
|
|
@@ -181,7 +181,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
181
181
|
async function main() {
|
|
182
182
|
const transport = new StdioServerTransport();
|
|
183
183
|
await server.connect(transport);
|
|
184
|
-
logger.info("🚀 MYX Trading MCP Server v2.
|
|
184
|
+
logger.info("🚀 MYX Trading MCP Server v2.4.0 running (stdio, pure on-chain, prod ready)");
|
|
185
185
|
}
|
|
186
186
|
main().catch((err) => {
|
|
187
187
|
logger.error("Fatal Server Startup Error", err);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { getChainId } from "../auth/resolveClient.js";
|
|
2
2
|
import { getMarketStateDesc } from "../utils/mappings.js";
|
|
3
|
-
export async function getMarketPrice(client, poolId) {
|
|
4
|
-
const chainId = getChainId();
|
|
3
|
+
export async function getMarketPrice(client, poolId, chainIdOverride) {
|
|
4
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
5
5
|
const ticker = await client.markets.getTickerList({
|
|
6
6
|
chainId,
|
|
7
7
|
poolIds: [poolId],
|
|
8
8
|
});
|
|
9
9
|
return ticker?.data?.[0] ?? null;
|
|
10
10
|
}
|
|
11
|
-
export async function getOraclePrice(client, poolId) {
|
|
12
|
-
const chainId = getChainId();
|
|
11
|
+
export async function getOraclePrice(client, poolId, chainIdOverride) {
|
|
12
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
13
13
|
return client.utils.getOraclePrice(poolId, chainId);
|
|
14
14
|
}
|
|
15
15
|
export async function searchMarket(client, keyword, limit = 1000) {
|
|
@@ -57,8 +57,8 @@ export async function searchMarket(client, keyword, limit = 1000) {
|
|
|
57
57
|
};
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
|
-
export async function getMarketDetail(client, poolId) {
|
|
61
|
-
const chainId = getChainId();
|
|
60
|
+
export async function getMarketDetail(client, poolId, chainIdOverride) {
|
|
61
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
62
62
|
const res = await client.markets.getMarketDetail({ chainId, poolId });
|
|
63
63
|
// Ensure it's returned as { data: ... } for consistency with other services if needed,
|
|
64
64
|
// but looking at existing tools, they often stringify the whole result.
|
|
@@ -74,7 +74,7 @@ export async function getPoolList(client) {
|
|
|
74
74
|
/**
|
|
75
75
|
* 获取池子分级配置
|
|
76
76
|
*/
|
|
77
|
-
export async function getPoolLevelConfig(client, poolId) {
|
|
78
|
-
const chainId = getChainId();
|
|
77
|
+
export async function getPoolLevelConfig(client, poolId, chainIdOverride) {
|
|
78
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
79
79
|
return client.api.getPoolLevelConfig({ poolId, chainId });
|
|
80
80
|
}
|
|
@@ -12,58 +12,58 @@ export async function createPool(baseToken, marketId) {
|
|
|
12
12
|
/**
|
|
13
13
|
* 获取池子信息
|
|
14
14
|
*/
|
|
15
|
-
export async function getPoolInfo(poolId) {
|
|
16
|
-
const chainId = getChainId();
|
|
15
|
+
export async function getPoolInfo(poolId, chainIdOverride) {
|
|
16
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
17
17
|
return pool.getPoolInfo(chainId, poolId);
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* 获取池子详情
|
|
21
21
|
*/
|
|
22
|
-
export async function getPoolDetail(poolId) {
|
|
23
|
-
const chainId = getChainId();
|
|
22
|
+
export async function getPoolDetail(poolId, chainIdOverride) {
|
|
23
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
24
24
|
return pool.getPoolDetail(chainId, poolId);
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* 获取流动性信息
|
|
28
28
|
*/
|
|
29
|
-
export async function getLiquidityInfo(client, poolId, marketPrice) {
|
|
30
|
-
const chainId = getChainId();
|
|
29
|
+
export async function getLiquidityInfo(client, poolId, marketPrice, chainIdOverride) {
|
|
30
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
31
31
|
const price30 = ensureUnits(marketPrice, 30, "marketPrice");
|
|
32
32
|
return client.utils.getLiquidityInfo({ chainId, poolId, marketPrice: price30 });
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* Quote 池 deposit
|
|
36
36
|
*/
|
|
37
|
-
export async function quoteDeposit(poolId, amount, slippage) {
|
|
38
|
-
const chainId = getChainId();
|
|
37
|
+
export async function quoteDeposit(poolId, amount, slippage, chainIdOverride) {
|
|
38
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
39
39
|
return quote.deposit({ chainId, poolId, amount, slippage });
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Quote 池 withdraw
|
|
43
43
|
*/
|
|
44
|
-
export async function quoteWithdraw(poolId, amount, slippage) {
|
|
45
|
-
const chainId = getChainId();
|
|
44
|
+
export async function quoteWithdraw(poolId, amount, slippage, chainIdOverride) {
|
|
45
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
46
46
|
return quote.withdraw({ chainId, poolId, amount, slippage });
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* Base 池 deposit
|
|
50
50
|
*/
|
|
51
|
-
export async function baseDeposit(poolId, amount, slippage) {
|
|
52
|
-
const chainId = getChainId();
|
|
51
|
+
export async function baseDeposit(poolId, amount, slippage, chainIdOverride) {
|
|
52
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
53
53
|
return base.deposit({ chainId, poolId, amount, slippage });
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
56
|
* Base 池 withdraw
|
|
57
57
|
*/
|
|
58
|
-
export async function baseWithdraw(poolId, amount, slippage) {
|
|
59
|
-
const chainId = getChainId();
|
|
58
|
+
export async function baseWithdraw(poolId, amount, slippage, chainIdOverride) {
|
|
59
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
60
60
|
return base.withdraw({ chainId, poolId, amount, slippage });
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
63
|
* 获取 LP 价格
|
|
64
64
|
*/
|
|
65
|
-
export async function getLpPrice(poolType, poolId) {
|
|
66
|
-
const chainId = getChainId();
|
|
65
|
+
export async function getLpPrice(poolType, poolId, chainIdOverride) {
|
|
66
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
67
67
|
if (poolType === "BASE") {
|
|
68
68
|
return base.getLpPrice(chainId, poolId);
|
|
69
69
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Direction, OrderType, TriggerType } from "@myx-trade/sdk";
|
|
2
2
|
import { parseUnits } from "ethers";
|
|
3
3
|
import { getChainId, getQuoteToken, getQuoteDecimals } from "../auth/resolveClient.js";
|
|
4
|
-
import { ensureUnits
|
|
4
|
+
import { ensureUnits } from "../utils/units.js";
|
|
5
5
|
import { normalizeAddress } from "../utils/address.js";
|
|
6
6
|
/**
|
|
7
7
|
* 将人类可读数值转为指定精度的整数字串
|
|
@@ -35,7 +35,7 @@ export async function openPosition(client, address, args) {
|
|
|
35
35
|
let calcPrice30;
|
|
36
36
|
let orderPrice30;
|
|
37
37
|
if (args.price) {
|
|
38
|
-
const limitPrice30 =
|
|
38
|
+
const limitPrice30 = ensureUnits(args.price, 30, "price");
|
|
39
39
|
calcPrice30 = limitPrice30;
|
|
40
40
|
orderPrice30 = limitPrice30;
|
|
41
41
|
}
|
|
@@ -46,7 +46,7 @@ export async function openPosition(client, address, args) {
|
|
|
46
46
|
console.error(`[Market Order] Fetched oracle price for size math: ${calcPrice30}`);
|
|
47
47
|
}
|
|
48
48
|
// 保证金转为 quote token 精度
|
|
49
|
-
const collateralWei =
|
|
49
|
+
const collateralWei = ensureUnits(args.collateralAmount, quoteDecimals, "collateralAmount");
|
|
50
50
|
// 仓位大小 (size): 在 MYX 这一版中,size 表示 Base Token(WETH)的数量,精度通常为 18
|
|
51
51
|
// 计算公式:size = (collateral * leverage) / price
|
|
52
52
|
// 为了防止浮点溢出,我们可以全部转为 BigInt 计算
|
|
@@ -85,11 +85,11 @@ export async function openPosition(client, address, args) {
|
|
|
85
85
|
};
|
|
86
86
|
// 可选止盈止损
|
|
87
87
|
if (args.tpPrice) {
|
|
88
|
-
orderParams.tpPrice =
|
|
88
|
+
orderParams.tpPrice = ensureUnits(args.tpPrice, 30, "tpPrice");
|
|
89
89
|
orderParams.tpSize = size;
|
|
90
90
|
}
|
|
91
91
|
if (args.slPrice) {
|
|
92
|
-
orderParams.slPrice =
|
|
92
|
+
orderParams.slPrice = ensureUnits(args.slPrice, 30, "slPrice");
|
|
93
93
|
orderParams.slSize = size;
|
|
94
94
|
}
|
|
95
95
|
// tradingFee: SDK 第二个参数
|
|
@@ -111,7 +111,7 @@ export async function closePosition(client, address, args) {
|
|
|
111
111
|
if (args.direction === undefined || args.direction === null) {
|
|
112
112
|
throw new Error("direction is required (0=LONG, 1=SHORT), must match position direction.");
|
|
113
113
|
}
|
|
114
|
-
const price30 =
|
|
114
|
+
const price30 = ensureUnits(args.price, 30, "price");
|
|
115
115
|
const sizeWei = ensureUnits(args.size, 18, "size");
|
|
116
116
|
const dir = resolveDirection(args.direction);
|
|
117
117
|
return client.order.createDecreaseOrder({
|
|
@@ -125,6 +125,7 @@ export async function closePosition(client, address, args) {
|
|
|
125
125
|
collateralAmount: "0",
|
|
126
126
|
size: sizeWei,
|
|
127
127
|
price: price30,
|
|
128
|
+
timeInForce: 0,
|
|
128
129
|
postOnly: false,
|
|
129
130
|
slippagePct: args.slippagePct ?? "100",
|
|
130
131
|
executionFeeToken: quoteToken,
|
|
@@ -151,14 +152,14 @@ export async function setPositionTpSl(client, address, args) {
|
|
|
151
152
|
executionFeeToken: quoteToken,
|
|
152
153
|
tpTriggerType: args.direction === 0 ? TriggerType.GTE : TriggerType.LTE,
|
|
153
154
|
slTriggerType: args.direction === 0 ? TriggerType.LTE : TriggerType.GTE,
|
|
154
|
-
slippagePct: "100",
|
|
155
|
+
slippagePct: args.slippagePct ?? "100",
|
|
155
156
|
};
|
|
156
157
|
if (args.tpPrice) {
|
|
157
|
-
params.tpPrice =
|
|
158
|
+
params.tpPrice = ensureUnits(args.tpPrice, 30, "tpPrice");
|
|
158
159
|
params.tpSize = args.tpSize || "0";
|
|
159
160
|
}
|
|
160
161
|
if (args.slPrice) {
|
|
161
|
-
params.slPrice =
|
|
162
|
+
params.slPrice = ensureUnits(args.slPrice, 30, "slPrice");
|
|
162
163
|
params.slSize = args.slSize || "0";
|
|
163
164
|
}
|
|
164
165
|
return client.order.createPositionTpSlOrder(params);
|
|
@@ -173,7 +174,7 @@ export async function adjustMargin(client, address, args) {
|
|
|
173
174
|
return client.position.adjustCollateral({
|
|
174
175
|
poolId: args.poolId,
|
|
175
176
|
positionId: args.positionId,
|
|
176
|
-
adjustAmount:
|
|
177
|
+
adjustAmount: ensureUnits(args.adjustAmount, quoteDecimals, "adjustAmount"),
|
|
177
178
|
quoteToken,
|
|
178
179
|
chainId,
|
|
179
180
|
address,
|
|
@@ -234,14 +235,14 @@ export async function updateOrderTpSl(client, address, args) {
|
|
|
234
235
|
const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
|
|
235
236
|
const params = {
|
|
236
237
|
orderId: args.orderId,
|
|
237
|
-
tpSize: args.tpSize
|
|
238
|
-
tpPrice: args.tpPrice ?
|
|
239
|
-
slSize: args.slSize
|
|
240
|
-
slPrice: args.slPrice ?
|
|
238
|
+
tpSize: args.tpSize ? ensureUnits(args.tpSize, 18, "tpSize") : "0",
|
|
239
|
+
tpPrice: args.tpPrice ? ensureUnits(args.tpPrice, 30, "tpPrice") : "0",
|
|
240
|
+
slSize: args.slSize ? ensureUnits(args.slSize, 18, "slSize") : "0",
|
|
241
|
+
slPrice: args.slPrice ? ensureUnits(args.slPrice, 30, "slPrice") : "0",
|
|
241
242
|
useOrderCollateral: args.useOrderCollateral ?? true,
|
|
242
243
|
executionFeeToken: quoteToken,
|
|
243
|
-
size: args.size
|
|
244
|
-
price: args.price
|
|
244
|
+
size: args.size ? ensureUnits(args.size, 18, "size") : "0",
|
|
245
|
+
price: args.price ? ensureUnits(args.price, 30, "price") : "0",
|
|
245
246
|
};
|
|
246
247
|
return client.order.updateOrderTpSl(params, quoteToken, chainId, address, args.marketId, args.isTpSlOrder ?? true);
|
|
247
248
|
}
|
|
@@ -40,6 +40,7 @@ export const getTradeFlowTool = {
|
|
|
40
40
|
name: "get_trade_flow",
|
|
41
41
|
description: "Get account trade flow / transaction history.",
|
|
42
42
|
schema: {
|
|
43
|
+
poolId: z.string().optional().describe("Optional pool ID filter."),
|
|
43
44
|
page: z.coerce.number().optional().describe("Page number (default 1)"),
|
|
44
45
|
limit: z.coerce.number().optional().describe("Results per page (default 20)"),
|
|
45
46
|
},
|
|
@@ -47,7 +48,7 @@ export const getTradeFlowTool = {
|
|
|
47
48
|
try {
|
|
48
49
|
const { client, address } = await resolveClient();
|
|
49
50
|
const chainId = getChainId();
|
|
50
|
-
const query = { chainId, page: args.page ?? 1, limit: args.limit ?? 20 };
|
|
51
|
+
const query = { chainId, poolId: args.poolId, page: args.page ?? 1, limit: args.limit ?? 20 };
|
|
51
52
|
const result = await client.account.getTradeFlow(query, address);
|
|
52
53
|
const enhancedData = (result?.data || []).map((flow) => ({
|
|
53
54
|
...flow,
|
|
@@ -7,7 +7,7 @@ export const adjustMarginTool = {
|
|
|
7
7
|
schema: {
|
|
8
8
|
poolId: z.string().describe("Pool ID"),
|
|
9
9
|
positionId: z.string().describe("Position ID"),
|
|
10
|
-
adjustAmount: z.string().describe("
|
|
10
|
+
adjustAmount: z.string().describe("SDK standard: quote token raw units. Positive = add, negative = remove. Compatibility: human-readable decimals are also accepted."),
|
|
11
11
|
quoteToken: z.string().optional().describe("Quote token address"),
|
|
12
12
|
},
|
|
13
13
|
handler: async (args) => {
|
|
@@ -4,15 +4,16 @@ export const cancelOrderTool = {
|
|
|
4
4
|
name: "cancel_order",
|
|
5
5
|
description: "Cancel an open order by its order ID.",
|
|
6
6
|
schema: {
|
|
7
|
-
|
|
7
|
+
orderId: z.string().optional().describe("Single order ID to cancel"),
|
|
8
|
+
orderIds: z.array(z.string()).optional().describe("Array of order IDs to cancel"),
|
|
8
9
|
},
|
|
9
10
|
handler: async (args) => {
|
|
10
11
|
try {
|
|
11
12
|
const { client } = await resolveClient();
|
|
12
13
|
const chainId = getChainId();
|
|
13
|
-
const ids = args.orderIds;
|
|
14
|
+
const ids = args.orderId ? [args.orderId] : (Array.isArray(args.orderIds) ? args.orderIds : []);
|
|
14
15
|
if (!ids || ids.length === 0)
|
|
15
|
-
throw new Error("orderIds
|
|
16
|
+
throw new Error("orderId or orderIds is required.");
|
|
16
17
|
let result;
|
|
17
18
|
if (ids.length === 1) {
|
|
18
19
|
result = await client.order.cancelOrder(ids[0], chainId);
|
|
@@ -2,12 +2,13 @@ import { z } from "zod";
|
|
|
2
2
|
import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
|
|
3
3
|
import { getOraclePrice } from "../services/marketService.js";
|
|
4
4
|
import { ensureUnits } from "../utils/units.js";
|
|
5
|
+
import { bpsLikeString } from "../utils/schema.js";
|
|
5
6
|
export const closeAllPositionsTool = {
|
|
6
7
|
name: "close_all_positions",
|
|
7
8
|
description: "Emergency: close ALL open positions in a pool at once. Use for risk management.",
|
|
8
9
|
schema: {
|
|
9
10
|
poolId: z.string().describe("Pool ID to close all positions in"),
|
|
10
|
-
slippagePct:
|
|
11
|
+
slippagePct: bpsLikeString.optional().describe("Slippage in bps (4-digit precision): 1 = 0.01%, default 200 = 2% for emergency"),
|
|
11
12
|
},
|
|
12
13
|
handler: async (args) => {
|
|
13
14
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient } from "../auth/resolveClient.js";
|
|
3
3
|
import { closePosition as closePos } from "../services/tradeService.js";
|
|
4
|
+
import { bpsLikeString } from "../utils/schema.js";
|
|
4
5
|
export const closePositionTool = {
|
|
5
6
|
name: "close_position",
|
|
6
7
|
description: "Close an open position.",
|
|
@@ -11,9 +12,9 @@ export const closePositionTool = {
|
|
|
11
12
|
message: "direction must be 0 (LONG) or 1 (SHORT)",
|
|
12
13
|
}).describe("0 = LONG, 1 = SHORT (must match position direction)"),
|
|
13
14
|
leverage: z.coerce.number().describe("Position leverage"),
|
|
14
|
-
size: z.string().describe("
|
|
15
|
-
price: z.string().describe("
|
|
16
|
-
slippagePct:
|
|
15
|
+
size: z.string().describe("SDK standard: 18-decimal raw size. Compatibility: human-readable decimals are also accepted."),
|
|
16
|
+
price: z.string().describe("SDK standard: 30-decimal raw price. Compatibility: human-readable decimals are also accepted."),
|
|
17
|
+
slippagePct: bpsLikeString.optional().describe("Slippage in bps (4-digit precision): 1 = 0.01%, default 100 = 1%"),
|
|
17
18
|
quoteToken: z.string().optional().describe("Quote token address"),
|
|
18
19
|
},
|
|
19
20
|
handler: async (args) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient } from "../auth/resolveClient.js";
|
|
3
3
|
import { openPosition } from "../services/tradeService.js";
|
|
4
|
+
import { bpsLikeString } from "../utils/schema.js";
|
|
4
5
|
export const executeTradeTool = {
|
|
5
6
|
name: "execute_trade",
|
|
6
7
|
description: "Execute a new trade or add to an existing position.",
|
|
@@ -9,15 +10,15 @@ export const executeTradeTool = {
|
|
|
9
10
|
direction: z.coerce.number().int().refine((value) => value === 0 || value === 1, {
|
|
10
11
|
message: "direction must be 0 (LONG) or 1 (SHORT)",
|
|
11
12
|
}).describe("0 = LONG, 1 = SHORT"),
|
|
12
|
-
collateralAmount: z.string().describe("
|
|
13
|
+
collateralAmount: z.string().describe("SDK standard: quote token raw units. Compatibility: human-readable decimals are also accepted."),
|
|
13
14
|
leverage: z.coerce.number().optional().describe("Leverage multiplier (e.g. 10, default 10)"),
|
|
14
|
-
price: z.string().optional().describe("
|
|
15
|
+
price: z.string().optional().describe("SDK standard: 30-decimal raw price for LIMIT orders. Compatibility: human-readable decimals are also accepted."),
|
|
15
16
|
quoteToken: z.string().optional().describe("Quote token address. Uses env default if omitted."),
|
|
16
17
|
quoteDecimals: z.coerce.number().optional().describe("Quote token decimals (default 6)."),
|
|
17
|
-
slippagePct:
|
|
18
|
+
slippagePct: bpsLikeString.optional().describe("Slippage in bps (4-digit precision): 1 = 0.01%, default 100 = 1%"),
|
|
18
19
|
positionId: z.string().optional().describe("Existing position ID to add to. Default '0' = new position."),
|
|
19
|
-
tpPrice: z.string().optional().describe("
|
|
20
|
-
slPrice: z.string().optional().describe("
|
|
20
|
+
tpPrice: z.string().optional().describe("SDK standard: 30-decimal raw TP price. Compatibility: human-readable decimals are also accepted."),
|
|
21
|
+
slPrice: z.string().optional().describe("SDK standard: 30-decimal raw SL price. Compatibility: human-readable decimals are also accepted."),
|
|
21
22
|
},
|
|
22
23
|
handler: async (args) => {
|
|
23
24
|
try {
|
|
@@ -1,17 +1,71 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
+
function normalizeLower(value) {
|
|
4
|
+
return String(value ?? "").trim().toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
function collectRows(input) {
|
|
7
|
+
if (Array.isArray(input)) {
|
|
8
|
+
return input.flatMap((item) => collectRows(item));
|
|
9
|
+
}
|
|
10
|
+
if (!input || typeof input !== "object") {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
if (input.poolId || input.pool_id) {
|
|
14
|
+
return [input];
|
|
15
|
+
}
|
|
16
|
+
return Object.values(input).flatMap((value) => collectRows(value));
|
|
17
|
+
}
|
|
18
|
+
function findPoolIdByBaseAddress(rows, baseAddress) {
|
|
19
|
+
const target = normalizeLower(baseAddress);
|
|
20
|
+
if (!target)
|
|
21
|
+
return null;
|
|
22
|
+
for (const row of rows) {
|
|
23
|
+
const candidates = [
|
|
24
|
+
row.baseToken,
|
|
25
|
+
row.base_token,
|
|
26
|
+
row.baseAddress,
|
|
27
|
+
row.base_address,
|
|
28
|
+
row.base,
|
|
29
|
+
row.tokenAddress,
|
|
30
|
+
row.token_address,
|
|
31
|
+
];
|
|
32
|
+
const hit = candidates.some((candidate) => normalizeLower(candidate) === target);
|
|
33
|
+
if (!hit)
|
|
34
|
+
continue;
|
|
35
|
+
const poolId = row.poolId || row.pool_id;
|
|
36
|
+
if (poolId) {
|
|
37
|
+
return String(poolId);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
3
42
|
export const getBaseDetailTool = {
|
|
4
43
|
name: "get_base_detail",
|
|
5
|
-
description: "Get base asset detail metrics for
|
|
44
|
+
description: "Get base asset detail metrics. SDK doc uses baseAddress; MCP also accepts poolId for compatibility.",
|
|
6
45
|
schema: {
|
|
7
|
-
|
|
46
|
+
baseAddress: z.string().optional().describe("SDK doc parameter: base token address."),
|
|
47
|
+
poolId: z.string().optional().describe("Compatibility parameter: pool ID."),
|
|
8
48
|
chainId: z.coerce.number().optional().describe("Optional chainId override"),
|
|
9
49
|
},
|
|
10
50
|
handler: async (args) => {
|
|
11
51
|
try {
|
|
12
52
|
const { client } = await resolveClient();
|
|
13
53
|
const chainId = args.chainId ?? getChainId();
|
|
14
|
-
|
|
54
|
+
let poolId = args.poolId;
|
|
55
|
+
if (!poolId && args.baseAddress) {
|
|
56
|
+
const sources = await Promise.allSettled([
|
|
57
|
+
client.api.getPoolList(),
|
|
58
|
+
client.api.getMarketList(),
|
|
59
|
+
]);
|
|
60
|
+
const rows = sources
|
|
61
|
+
.filter((result) => result.status === "fulfilled")
|
|
62
|
+
.flatMap((result) => collectRows(result.value));
|
|
63
|
+
poolId = findPoolIdByBaseAddress(rows, args.baseAddress) || undefined;
|
|
64
|
+
}
|
|
65
|
+
if (!poolId) {
|
|
66
|
+
throw new Error("poolId is required. If using baseAddress, no matching pool was found.");
|
|
67
|
+
}
|
|
68
|
+
const result = await client.markets.getBaseDetail({ chainId, poolId });
|
|
15
69
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
|
|
16
70
|
}
|
|
17
71
|
catch (error) {
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { resolveClient } from "../auth/resolveClient.js";
|
|
2
|
+
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
3
|
import { getMarketPrice } from "../services/marketService.js";
|
|
4
4
|
export const getMarketPriceTool = {
|
|
5
5
|
name: "get_market_price",
|
|
6
6
|
description: "Get the current market price for a specific pool.",
|
|
7
7
|
schema: {
|
|
8
8
|
poolId: z.string().describe("Pool ID to get price for"),
|
|
9
|
+
chainId: z.coerce.number().optional().describe("Optional chainId override"),
|
|
9
10
|
},
|
|
10
11
|
handler: async (args) => {
|
|
11
12
|
try {
|
|
12
13
|
const { client } = await resolveClient();
|
|
13
|
-
const
|
|
14
|
+
const chainId = args.chainId ?? getChainId();
|
|
15
|
+
const data = await getMarketPrice(client, args.poolId, chainId);
|
|
14
16
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
15
17
|
}
|
|
16
18
|
catch (error) {
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { resolveClient } from "../auth/resolveClient.js";
|
|
2
|
+
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
3
|
import { getOraclePrice } from "../services/marketService.js";
|
|
4
4
|
export const getOraclePriceTool = {
|
|
5
5
|
name: "get_oracle_price",
|
|
6
6
|
description: "Get the current oracle price for a specific pool.",
|
|
7
7
|
schema: {
|
|
8
8
|
poolId: z.string().describe("Pool ID to get oracle price for"),
|
|
9
|
+
chainId: z.coerce.number().optional().describe("Optional chainId override"),
|
|
9
10
|
},
|
|
10
11
|
handler: async (args) => {
|
|
11
12
|
try {
|
|
12
13
|
const { client } = await resolveClient();
|
|
13
|
-
const
|
|
14
|
+
const chainId = args.chainId ?? getChainId();
|
|
15
|
+
const data = await getOraclePrice(client, args.poolId, chainId);
|
|
14
16
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
15
17
|
}
|
|
16
18
|
catch (error) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { quoteDeposit, quoteWithdraw, baseDeposit, baseWithdraw, getLpPrice, } from "../services/poolService.js";
|
|
3
3
|
import { parseSafeNumber } from "../utils/units.js";
|
|
4
|
+
import { lpSlippageLike } from "../utils/schema.js";
|
|
4
5
|
export const manageLiquidityTool = {
|
|
5
6
|
name: "manage_liquidity",
|
|
6
|
-
description: "Add or withdraw liquidity from a BASE or QUOTE pool. Amount is in human-readable units
|
|
7
|
+
description: "Add or withdraw liquidity from a BASE or QUOTE pool. Amount is in human-readable units. SDK-standard slippage is ratio (0.01 = 1%), and BPS input is auto-compatible.",
|
|
7
8
|
schema: {
|
|
8
9
|
action: z.string().trim().toLowerCase().refine((value) => value === "deposit" || value === "withdraw", {
|
|
9
10
|
message: "action must be 'deposit' or 'withdraw'.",
|
|
@@ -13,13 +14,14 @@ export const manageLiquidityTool = {
|
|
|
13
14
|
}).describe("'BASE' or 'QUOTE'"),
|
|
14
15
|
poolId: z.string().describe("Pool ID"),
|
|
15
16
|
amount: z.union([z.number(), z.string()]).describe("Amount in human-readable units (e.g. 2000 for USDC, 0.01 for ETH)"),
|
|
16
|
-
slippage:
|
|
17
|
+
slippage: lpSlippageLike.describe("SDK-standard LP slippage ratio (0.01 = 1%). BPS input is also accepted for compatibility (100 => 1%)."),
|
|
18
|
+
chainId: z.coerce.number().optional().describe("Optional chainId override"),
|
|
17
19
|
},
|
|
18
20
|
handler: async (args) => {
|
|
19
21
|
try {
|
|
20
22
|
const { action, poolType, poolId } = args;
|
|
21
23
|
const amount = parseSafeNumber(args.amount, "amount");
|
|
22
|
-
const slippage =
|
|
24
|
+
const slippage = args.slippage;
|
|
23
25
|
if (amount <= 0)
|
|
24
26
|
throw new Error("amount must be a positive number.");
|
|
25
27
|
if (slippage < 0)
|
|
@@ -27,13 +29,13 @@ export const manageLiquidityTool = {
|
|
|
27
29
|
let result;
|
|
28
30
|
if (poolType === "QUOTE") {
|
|
29
31
|
result = action === "deposit"
|
|
30
|
-
? await quoteDeposit(poolId, amount, slippage)
|
|
31
|
-
: await quoteWithdraw(poolId, amount, slippage);
|
|
32
|
+
? await quoteDeposit(poolId, amount, slippage, args.chainId)
|
|
33
|
+
: await quoteWithdraw(poolId, amount, slippage, args.chainId);
|
|
32
34
|
}
|
|
33
35
|
else {
|
|
34
36
|
result = action === "deposit"
|
|
35
|
-
? await baseDeposit(poolId, amount, slippage)
|
|
36
|
-
: await baseWithdraw(poolId, amount, slippage);
|
|
37
|
+
? await baseDeposit(poolId, amount, slippage, args.chainId)
|
|
38
|
+
: await baseWithdraw(poolId, amount, slippage, args.chainId);
|
|
37
39
|
}
|
|
38
40
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
39
41
|
}
|
|
@@ -50,10 +52,11 @@ export const getLpPriceTool = {
|
|
|
50
52
|
message: "poolType must be 'BASE' or 'QUOTE'.",
|
|
51
53
|
}).describe("'BASE' or 'QUOTE'"),
|
|
52
54
|
poolId: z.string().describe("Pool ID"),
|
|
55
|
+
chainId: z.coerce.number().optional().describe("Optional chainId override"),
|
|
53
56
|
},
|
|
54
57
|
handler: async (args) => {
|
|
55
58
|
try {
|
|
56
|
-
const data = await getLpPrice(args.poolType, args.poolId);
|
|
59
|
+
const data = await getLpPrice(args.poolType, args.poolId, args.chainId);
|
|
57
60
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
58
61
|
}
|
|
59
62
|
catch (error) {
|
package/dist/tools/marketInfo.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { resolveClient } from "../auth/resolveClient.js";
|
|
2
|
+
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
3
|
import { getMarketDetail } from "../services/marketService.js";
|
|
4
4
|
import { getPoolInfo, getLiquidityInfo } from "../services/poolService.js";
|
|
5
5
|
export const getMarketDetailTool = {
|
|
@@ -7,11 +7,13 @@ export const getMarketDetailTool = {
|
|
|
7
7
|
description: "Get detailed information for a specific trading pool (fee rates, open interest, etc.).",
|
|
8
8
|
schema: {
|
|
9
9
|
poolId: z.string().describe("Pool ID"),
|
|
10
|
+
chainId: z.coerce.number().optional().describe("Optional chainId override"),
|
|
10
11
|
},
|
|
11
12
|
handler: async (args) => {
|
|
12
13
|
try {
|
|
13
14
|
const { client } = await resolveClient();
|
|
14
|
-
const
|
|
15
|
+
const chainId = args.chainId ?? getChainId();
|
|
16
|
+
const data = await getMarketDetail(client, args.poolId, chainId);
|
|
15
17
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
16
18
|
}
|
|
17
19
|
catch (error) {
|
|
@@ -24,10 +26,12 @@ export const getPoolInfoTool = {
|
|
|
24
26
|
description: "Get pool on-chain information (reserves, utilization, etc.).",
|
|
25
27
|
schema: {
|
|
26
28
|
poolId: z.string().describe("Pool ID"),
|
|
29
|
+
chainId: z.coerce.number().optional().describe("Optional chainId override"),
|
|
27
30
|
},
|
|
28
31
|
handler: async (args) => {
|
|
29
32
|
try {
|
|
30
|
-
const
|
|
33
|
+
const chainId = args.chainId ?? getChainId();
|
|
34
|
+
const data = await getPoolInfo(args.poolId, chainId);
|
|
31
35
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
32
36
|
}
|
|
33
37
|
catch (error) {
|
|
@@ -41,11 +45,13 @@ export const getLiquidityInfoTool = {
|
|
|
41
45
|
schema: {
|
|
42
46
|
poolId: z.string().describe("Pool ID"),
|
|
43
47
|
marketPrice: z.string().describe("Current market price (can be human-readable e.g. '0.69' or 30-decimal raw units)"),
|
|
48
|
+
chainId: z.coerce.number().optional().describe("Optional chainId override"),
|
|
44
49
|
},
|
|
45
50
|
handler: async (args) => {
|
|
46
51
|
try {
|
|
47
52
|
const { client } = await resolveClient();
|
|
48
|
-
const
|
|
53
|
+
const chainId = args.chainId ?? getChainId();
|
|
54
|
+
const data = await getLiquidityInfo(client, args.poolId, args.marketPrice, chainId);
|
|
49
55
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
50
56
|
}
|
|
51
57
|
catch (error) {
|
package/dist/tools/poolConfig.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { resolveClient } from "../auth/resolveClient.js";
|
|
2
|
+
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
3
|
import { getPoolLevelConfig } from "../services/marketService.js";
|
|
4
4
|
export const getPoolLevelConfigTool = {
|
|
5
5
|
name: "get_pool_level_config",
|
|
6
6
|
description: "Get the level configuration and trading limits for a specific pool.",
|
|
7
7
|
schema: {
|
|
8
8
|
poolId: z.string().describe("Pool ID"),
|
|
9
|
+
chainId: z.coerce.number().optional().describe("Optional chainId override"),
|
|
9
10
|
},
|
|
10
11
|
handler: async (args) => {
|
|
11
12
|
try {
|
|
12
13
|
const { client } = await resolveClient();
|
|
13
|
-
const
|
|
14
|
+
const chainId = args.chainId ?? getChainId();
|
|
15
|
+
const result = await getPoolLevelConfig(client, args.poolId, chainId);
|
|
14
16
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
15
17
|
}
|
|
16
18
|
catch (error) {
|
package/dist/tools/setTpSl.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient } from "../auth/resolveClient.js";
|
|
3
3
|
import { setPositionTpSl } from "../services/tradeService.js";
|
|
4
|
+
import { bpsLikeString } from "../utils/schema.js";
|
|
4
5
|
export const setTpSlTool = {
|
|
5
6
|
name: "set_tp_sl",
|
|
6
7
|
description: "Set take profit and stop loss prices for an open position.",
|
|
@@ -11,10 +12,11 @@ export const setTpSlTool = {
|
|
|
11
12
|
message: "direction must be 0 (LONG) or 1 (SHORT)",
|
|
12
13
|
}).describe("0 = LONG, 1 = SHORT (position direction)"),
|
|
13
14
|
leverage: z.coerce.number().describe("Position leverage"),
|
|
14
|
-
tpPrice: z.string().optional().describe("
|
|
15
|
-
tpSize: z.string().optional().describe("
|
|
16
|
-
slPrice: z.string().optional().describe("
|
|
17
|
-
slSize: z.string().optional().describe("
|
|
15
|
+
tpPrice: z.string().optional().describe("SDK standard: 30-decimal raw TP price. Compatibility: human-readable decimals are also accepted."),
|
|
16
|
+
tpSize: z.string().optional().describe("SDK standard: 18-decimal raw TP size. Omit or '0' for full position."),
|
|
17
|
+
slPrice: z.string().optional().describe("SDK standard: 30-decimal raw SL price. Compatibility: human-readable decimals are also accepted."),
|
|
18
|
+
slSize: z.string().optional().describe("SDK standard: 18-decimal raw SL size. Omit or '0' for full position."),
|
|
19
|
+
slippagePct: bpsLikeString.optional().describe("Slippage in bps (4-digit precision): 1 = 0.01%, default 100 = 1%"),
|
|
18
20
|
quoteToken: z.string().optional().describe("Quote token address"),
|
|
19
21
|
},
|
|
20
22
|
handler: async (args) => {
|
|
@@ -8,10 +8,13 @@ export const updateOrderTpSlTool = {
|
|
|
8
8
|
schema: {
|
|
9
9
|
orderId: z.string().describe("The ID of the order to update"),
|
|
10
10
|
marketId: z.string().describe("The market ID (config hash) for the order"),
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
size: z.string().optional().describe("SDK standard: 18-decimal raw order size."),
|
|
12
|
+
price: z.string().optional().describe("SDK standard: 30-decimal raw order price."),
|
|
13
|
+
tpPrice: z.string().optional().describe("SDK standard: 30-decimal raw TP price. Compatibility: human-readable decimals are also accepted."),
|
|
14
|
+
tpSize: z.string().optional().describe("SDK standard: 18-decimal raw TP size. Omit or '0' for full position."),
|
|
15
|
+
slPrice: z.string().optional().describe("SDK standard: 30-decimal raw SL price. Compatibility: human-readable decimals are also accepted."),
|
|
16
|
+
slSize: z.string().optional().describe("SDK standard: 18-decimal raw SL size. Omit or '0' for full position."),
|
|
17
|
+
useOrderCollateral: booleanLike.optional().describe("Whether to use the order collateral (SDK default true)."),
|
|
15
18
|
isTpSlOrder: booleanLike.optional().describe("Whether this is a TP/SL order (default true)"),
|
|
16
19
|
quoteToken: z.string().optional().describe("Quote token address"),
|
|
17
20
|
},
|
package/dist/utils/schema.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
const trueValues = new Set(["true", "1", "yes", "y", "on"]);
|
|
3
3
|
const falseValues = new Set(["false", "0", "no", "n", "off"]);
|
|
4
|
+
const DECIMAL_RE = /^-?\d+(\.\d+)?$/;
|
|
5
|
+
const EPSILON = 1e-8;
|
|
4
6
|
/**
|
|
5
7
|
* Safe boolean coercion for MCP inputs:
|
|
6
8
|
* - boolean: true/false
|
|
@@ -16,3 +18,67 @@ export const booleanLike = z.union([
|
|
|
16
18
|
message: "Expected boolean-like value: true/false/1/0/yes/no/on/off.",
|
|
17
19
|
}).transform((value) => trueValues.has(value)),
|
|
18
20
|
]);
|
|
21
|
+
function isNearlyInteger(value) {
|
|
22
|
+
return Math.abs(value - Math.round(value)) < EPSILON;
|
|
23
|
+
}
|
|
24
|
+
function parseBpsLike(value) {
|
|
25
|
+
const raw = String(value).trim();
|
|
26
|
+
if (!raw)
|
|
27
|
+
throw new Error("slippage is required.");
|
|
28
|
+
if (!DECIMAL_RE.test(raw))
|
|
29
|
+
throw new Error("slippage must be numeric.");
|
|
30
|
+
const parsed = Number(raw);
|
|
31
|
+
if (!Number.isFinite(parsed))
|
|
32
|
+
throw new Error("slippage must be finite.");
|
|
33
|
+
if (parsed < 0)
|
|
34
|
+
throw new Error("slippage must be >= 0.");
|
|
35
|
+
if (isNearlyInteger(parsed)) {
|
|
36
|
+
return Math.round(parsed);
|
|
37
|
+
}
|
|
38
|
+
// Backward compatibility: legacy ratio input (e.g. 0.01 = 1%) -> bps
|
|
39
|
+
if (parsed > 0 && parsed < 1) {
|
|
40
|
+
const bps = parsed * 10000;
|
|
41
|
+
if (!isNearlyInteger(bps)) {
|
|
42
|
+
throw new Error("legacy slippage ratio is too precise; use integer bps.");
|
|
43
|
+
}
|
|
44
|
+
return Math.round(bps);
|
|
45
|
+
}
|
|
46
|
+
throw new Error("slippage must be integer bps (1 = 0.01%).");
|
|
47
|
+
}
|
|
48
|
+
function parseLpSlippageLike(value) {
|
|
49
|
+
const raw = String(value).trim();
|
|
50
|
+
if (!raw)
|
|
51
|
+
throw new Error("slippage is required.");
|
|
52
|
+
if (!DECIMAL_RE.test(raw))
|
|
53
|
+
throw new Error("slippage must be numeric.");
|
|
54
|
+
const parsed = Number(raw);
|
|
55
|
+
if (!Number.isFinite(parsed))
|
|
56
|
+
throw new Error("slippage must be finite.");
|
|
57
|
+
if (parsed < 0)
|
|
58
|
+
throw new Error("slippage must be >= 0.");
|
|
59
|
+
// SDK doc convention for LP: ratio (e.g. 0.01 = 1%)
|
|
60
|
+
if (parsed <= 1) {
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
// Backward compatibility for MCP callers that send BPS (e.g. 100 = 1%)
|
|
64
|
+
return parsed / 10000;
|
|
65
|
+
}
|
|
66
|
+
export const bpsLikeNumber = z.union([z.number(), z.string()]).transform((value, ctx) => {
|
|
67
|
+
try {
|
|
68
|
+
return parseBpsLike(value);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
ctx.addIssue({ code: "custom", message: error.message || "Invalid slippage." });
|
|
72
|
+
return z.NEVER;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
export const bpsLikeString = bpsLikeNumber.transform((value) => value.toString());
|
|
76
|
+
export const lpSlippageLike = z.union([z.number(), z.string()]).transform((value, ctx) => {
|
|
77
|
+
try {
|
|
78
|
+
return parseLpSlippageLike(value);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
ctx.addIssue({ code: "custom", message: error.message || "Invalid LP slippage." });
|
|
82
|
+
return z.NEVER;
|
|
83
|
+
}
|
|
84
|
+
});
|