@michaleffffff/mcp-trading-server 2.9.0 → 2.9.9
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 +7 -1
- package/dist/services/tradeService.js +13 -5
- package/dist/tools/closePosition.js +9 -1
- package/dist/tools/executeTrade.js +11 -1
- package/dist/tools/getPositions.js +58 -4
- package/dist/tools/manageLiquidity.js +19 -8
- package/dist/tools/marketInfo.js +20 -10
- package/dist/tools/setTpSl.js +10 -4
- package/dist/utils/mutationResult.js +29 -12
- 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.9.
|
|
84
|
+
const server = new Server({ name: "myx-mcp-trading-server", version: "2.9.9" }, { 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.9.
|
|
184
|
+
logger.info("🚀 MYX Trading MCP Server v2.9.9 running (stdio, pure on-chain, prod ready)");
|
|
185
185
|
}
|
|
186
186
|
main().catch((err) => {
|
|
187
187
|
logger.error("Fatal Server Startup Error", err);
|
|
@@ -128,7 +128,13 @@ export async function resolvePool(client, poolId, keyword) {
|
|
|
128
128
|
const base = String(row?.baseSymbol ?? "").toUpperCase();
|
|
129
129
|
const pair = String(row?.baseQuoteSymbol ?? "").toUpperCase();
|
|
130
130
|
const id = String(row?.poolId ?? row?.pool_id ?? "").toUpperCase();
|
|
131
|
-
|
|
131
|
+
const baseToken = String(row?.baseToken ?? row?.base_token ?? "").toUpperCase();
|
|
132
|
+
const quoteToken = String(row?.quoteToken ?? row?.quote_token ?? "").toUpperCase();
|
|
133
|
+
return base === searchKey ||
|
|
134
|
+
pair.includes(searchKey) ||
|
|
135
|
+
id === searchKey ||
|
|
136
|
+
baseToken === searchKey ||
|
|
137
|
+
quoteToken === searchKey;
|
|
132
138
|
});
|
|
133
139
|
if (match) {
|
|
134
140
|
return String(match.poolId ?? match.pool_id);
|
|
@@ -13,16 +13,24 @@ function resolveDirection(direction) {
|
|
|
13
13
|
/**
|
|
14
14
|
* 自动推断开启订单的触发类型 (Limit/Stop)
|
|
15
15
|
*/
|
|
16
|
-
function resolveTriggerType(orderType, direction, triggerType) {
|
|
16
|
+
function resolveTriggerType(orderType, direction, isDecrease = false, triggerType) {
|
|
17
17
|
if (triggerType !== undefined && triggerType !== null && triggerType !== 0) {
|
|
18
18
|
return triggerType;
|
|
19
19
|
}
|
|
20
|
-
// LIMIT LONG
|
|
20
|
+
// Opening: LIMIT LONG -> LTE(2), LIMIT SHORT -> GTE(1)
|
|
21
|
+
// Closing: LIMIT LONG -> GTE(1), LIMIT SHORT -> LTE(2)
|
|
21
22
|
if (orderType === OrderType.LIMIT) {
|
|
23
|
+
if (isDecrease) {
|
|
24
|
+
return direction === 0 ? 1 : 2;
|
|
25
|
+
}
|
|
22
26
|
return direction === 0 ? 2 : 1;
|
|
23
27
|
}
|
|
24
|
-
// STOP LONG
|
|
28
|
+
// Opening: STOP LONG -> GTE(1), STOP SHORT -> LTE(2)
|
|
29
|
+
// Closing: STOP LONG -> LTE(2), STOP SHORT -> GTE(1)
|
|
25
30
|
if (orderType === OrderType.STOP) {
|
|
31
|
+
if (isDecrease) {
|
|
32
|
+
return direction === 0 ? 2 : 1;
|
|
33
|
+
}
|
|
26
34
|
return direction === 0 ? 1 : 2;
|
|
27
35
|
}
|
|
28
36
|
return 0; // MARKET order typically uses 0
|
|
@@ -138,7 +146,7 @@ export async function openPosition(client, address, args) {
|
|
|
138
146
|
poolId: args.poolId,
|
|
139
147
|
positionId: args.positionId,
|
|
140
148
|
orderType: args.orderType,
|
|
141
|
-
triggerType: resolveTriggerType(args.orderType, args.direction, args.triggerType),
|
|
149
|
+
triggerType: resolveTriggerType(args.orderType, args.direction, false, args.triggerType),
|
|
142
150
|
direction: dir,
|
|
143
151
|
collateralAmount: collateralRaw,
|
|
144
152
|
size: sizeRaw,
|
|
@@ -184,7 +192,7 @@ export async function closePosition(client, address, args) {
|
|
|
184
192
|
poolId: args.poolId,
|
|
185
193
|
positionId: args.positionId,
|
|
186
194
|
orderType: args.orderType,
|
|
187
|
-
triggerType: resolveTriggerType(args.orderType, args.direction, args.triggerType),
|
|
195
|
+
triggerType: resolveTriggerType(args.orderType, args.direction, true, args.triggerType),
|
|
188
196
|
direction: dir,
|
|
189
197
|
collateralAmount: ensureUnits(args.collateralAmount, quoteDecimals, "collateralAmount"),
|
|
190
198
|
size: ensureUnits(args.size, baseDecimals, "size"),
|
|
@@ -19,18 +19,26 @@ export const closePositionTool = {
|
|
|
19
19
|
price: z.union([z.string(), z.number()]).describe("Price (human or 30-dec raw units)"),
|
|
20
20
|
timeInForce: z.coerce.number().int().describe("TimeInForce: 0=GTC, 1=IOC, 2=FOK"),
|
|
21
21
|
postOnly: z.coerce.boolean().describe("Post-only flag"),
|
|
22
|
-
slippagePct: z.coerce.string().describe(SLIPPAGE_PCT_4DP_DESC),
|
|
22
|
+
slippagePct: z.coerce.string().default("50").describe(SLIPPAGE_PCT_4DP_DESC),
|
|
23
23
|
executionFeeToken: z.string().describe("Execution fee token address"),
|
|
24
24
|
leverage: z.coerce.number().describe("Leverage"),
|
|
25
25
|
},
|
|
26
26
|
handler: async (args) => {
|
|
27
27
|
try {
|
|
28
28
|
const { client, address, signer } = await resolveClient();
|
|
29
|
+
const { chainId } = await resolveClient();
|
|
30
|
+
const poolId = args.poolId;
|
|
31
|
+
// Fetch pool detail to get quoteToken for execution fee
|
|
32
|
+
const poolResponse = await client.markets.getMarketDetail({ chainId, poolId });
|
|
33
|
+
const poolData = poolResponse?.data || (poolResponse?.marketId ? poolResponse : null);
|
|
34
|
+
if (!poolData)
|
|
35
|
+
throw new Error(`Could not find pool metadata for ID: ${poolId}`);
|
|
29
36
|
const mappedArgs = {
|
|
30
37
|
...args,
|
|
31
38
|
direction: mapDirection(args.direction),
|
|
32
39
|
orderType: mapOrderType(args.orderType),
|
|
33
40
|
triggerType: args.triggerType !== undefined ? mapTriggerType(args.triggerType) : undefined,
|
|
41
|
+
executionFeeToken: poolData.quoteToken || args.executionFeeToken
|
|
34
42
|
};
|
|
35
43
|
const raw = await closePos(client, address, mappedArgs);
|
|
36
44
|
const data = await finalizeMutationResult(raw, signer, "close_position");
|
|
@@ -22,7 +22,7 @@ export const executeTradeTool = {
|
|
|
22
22
|
price: z.union([z.string(), z.number()]).describe("Execution or Limit price. e.g. '65000' or 'raw:...'"),
|
|
23
23
|
timeInForce: z.coerce.number().int().describe("0=GTC, 1=IOC, 2=FOK"),
|
|
24
24
|
postOnly: z.coerce.boolean().describe("If true, order only executes as Maker."),
|
|
25
|
-
slippagePct: z.coerce.string().describe(`${SLIPPAGE_PCT_4DP_DESC}.
|
|
25
|
+
slippagePct: z.coerce.string().default("50").describe(`${SLIPPAGE_PCT_4DP_DESC}. Default is 50 (0.5%).`),
|
|
26
26
|
executionFeeToken: z.string().describe("Address of token to pay gas/execution fees (typically USDC)."),
|
|
27
27
|
leverage: z.coerce.number().describe("Leverage multiplier, e.g., 10 for 10x."),
|
|
28
28
|
tpSize: z.union([z.string(), z.number()]).optional().describe("Take Profit size. Use '0' to disable."),
|
|
@@ -35,11 +35,21 @@ export const executeTradeTool = {
|
|
|
35
35
|
handler: async (args) => {
|
|
36
36
|
try {
|
|
37
37
|
const { client, address, signer } = await resolveClient();
|
|
38
|
+
const poolId = args.poolId;
|
|
39
|
+
// Fetch pool detail to get quoteToken for execution fee
|
|
40
|
+
const poolResponse = await client.markets.getMarketDetail({ chainId: await resolveClient().then(r => r.chainId), poolId });
|
|
41
|
+
const poolData = poolResponse?.data || (poolResponse?.marketId ? poolResponse : null);
|
|
42
|
+
if (!poolData)
|
|
43
|
+
throw new Error(`Could not find pool metadata for ID: ${poolId}`);
|
|
38
44
|
const mappedArgs = {
|
|
39
45
|
...args,
|
|
40
46
|
direction: mapDirection(args.direction),
|
|
41
47
|
orderType: mapOrderType(args.orderType),
|
|
42
48
|
triggerType: args.triggerType !== undefined ? mapTriggerType(args.triggerType) : undefined,
|
|
49
|
+
// Normalize positionId
|
|
50
|
+
positionId: (args.positionId === "0" || !args.positionId) ? "" : args.positionId,
|
|
51
|
+
// Enforce executionFeeToken as quoteToken
|
|
52
|
+
executionFeeToken: poolData.quoteToken || args.executionFeeToken
|
|
43
53
|
};
|
|
44
54
|
const raw = await openPosition(client, address, mappedArgs);
|
|
45
55
|
const data = await finalizeMutationResult(raw, signer, "execute_trade");
|
|
@@ -7,13 +7,67 @@ export const getPositionsTool = {
|
|
|
7
7
|
schema: {},
|
|
8
8
|
handler: async () => {
|
|
9
9
|
try {
|
|
10
|
-
const { client, address } = await resolveClient();
|
|
10
|
+
const { client, address, chainId } = await resolveClient();
|
|
11
11
|
const data = await getPositions(client, address);
|
|
12
12
|
const positions = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
if (positions.length === 0) {
|
|
14
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: [] }) }] };
|
|
15
|
+
}
|
|
16
|
+
// 1. Fetch Tickers for all relevant pools
|
|
17
|
+
const poolIds = [...new Set(positions.map((p) => p.poolId))];
|
|
18
|
+
const tickersRes = await client.markets.getTickerList({ chainId, poolIds });
|
|
19
|
+
const tickers = Array.isArray(tickersRes) ? tickersRes : (tickersRes?.data ?? []);
|
|
20
|
+
// 2. Fetch Pool Level Configs to get MM (Maintenance Margin Rate)
|
|
21
|
+
const configs = await Promise.all(poolIds.map(async (pid) => {
|
|
22
|
+
try {
|
|
23
|
+
const res = await client.markets.getPoolLevelConfig(pid, chainId);
|
|
24
|
+
return { poolId: pid, config: res?.levelConfig || res?.data?.levelConfig };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { poolId: pid, config: null };
|
|
28
|
+
}
|
|
16
29
|
}));
|
|
30
|
+
const enhancedData = positions.map((pos) => {
|
|
31
|
+
const ticker = tickers.find((t) => t.poolId === pos.poolId);
|
|
32
|
+
const currentPrice = Number(ticker?.price || 0);
|
|
33
|
+
const entryPrice = Number(pos.entryPrice || 0);
|
|
34
|
+
const size = Number(pos.size || 0);
|
|
35
|
+
const collateral = Number(pos.collateralAmount || 0);
|
|
36
|
+
const direction = pos.direction; // 0 = LONG, 1 = SHORT
|
|
37
|
+
const mm = configs.find(c => c.poolId === pos.poolId)?.config?.maintainCollateralRate || 0.02;
|
|
38
|
+
// Estimated PnL
|
|
39
|
+
let estimatedPnl = 0;
|
|
40
|
+
if (direction === 0) { // LONG
|
|
41
|
+
estimatedPnl = (currentPrice - entryPrice) * size;
|
|
42
|
+
}
|
|
43
|
+
else { // SHORT
|
|
44
|
+
estimatedPnl = (entryPrice - currentPrice) * size;
|
|
45
|
+
}
|
|
46
|
+
// ROI
|
|
47
|
+
const roi = collateral > 0 ? (estimatedPnl / collateral) * 100 : 0;
|
|
48
|
+
// Liquidation Price
|
|
49
|
+
// Long: (Entry * Size - Collateral) / (Size * (1 - MM))
|
|
50
|
+
// Short: (Entry * Size + Collateral) / (Size * (1 + MM))
|
|
51
|
+
let liqPrice = 0;
|
|
52
|
+
if (size > 0) {
|
|
53
|
+
if (direction === 0) {
|
|
54
|
+
liqPrice = (entryPrice * size - collateral) / (size * (1 - mm));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
liqPrice = (entryPrice * size + collateral) / (size * (1 + mm));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (liqPrice < 0)
|
|
61
|
+
liqPrice = 0;
|
|
62
|
+
return {
|
|
63
|
+
...pos,
|
|
64
|
+
directionDesc: getDirectionDesc(pos.direction),
|
|
65
|
+
currentPrice: currentPrice.toString(),
|
|
66
|
+
estimatedPnl: estimatedPnl.toFixed(4),
|
|
67
|
+
roi: roi.toFixed(2) + "%",
|
|
68
|
+
liquidationPrice: liqPrice.toFixed(4)
|
|
69
|
+
};
|
|
70
|
+
});
|
|
17
71
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: enhancedData }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
18
72
|
}
|
|
19
73
|
catch (error) {
|
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { quoteDeposit, quoteWithdraw, baseDeposit, baseWithdraw, getLpPrice, } from "../services/poolService.js";
|
|
3
3
|
import { resolveClient } from "../auth/resolveClient.js";
|
|
4
|
+
import { resolvePool } from "../services/marketService.js";
|
|
4
5
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
5
6
|
export const manageLiquidityTool = {
|
|
6
7
|
name: "manage_liquidity",
|
|
7
8
|
description: "Add or withdraw liquidity from a BASE or QUOTE pool.",
|
|
8
9
|
schema: {
|
|
9
|
-
action: z.enum(["deposit", "withdraw"]).describe("'deposit' or 'withdraw'"),
|
|
10
|
+
action: z.enum(["deposit", "withdraw", "add", "remove", "increase", "decrease"]).describe("'deposit' or 'withdraw' (aliases: add, remove, increase, decrease)"),
|
|
10
11
|
poolType: z.enum(["BASE", "QUOTE"]).describe("'BASE' or 'QUOTE'"),
|
|
11
|
-
poolId: z.string().describe("Pool ID"),
|
|
12
|
-
amount: z.number().positive().describe("Amount in human-readable units"),
|
|
13
|
-
slippage: z.number().min(0).describe("LP slippage ratio (e.g. 0.01 = 1%)"),
|
|
14
|
-
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
12
|
+
poolId: z.string().describe("Pool ID or Base Token Address"),
|
|
13
|
+
amount: z.coerce.number().positive().describe("Amount in human-readable units"),
|
|
14
|
+
slippage: z.coerce.number().min(0).describe("LP slippage ratio (e.g. 0.01 = 1%)"),
|
|
15
|
+
chainId: z.coerce.number().int().positive().optional().describe("Optional chainId override"),
|
|
15
16
|
},
|
|
16
17
|
handler: async (args) => {
|
|
17
18
|
try {
|
|
18
|
-
const { signer } = await resolveClient();
|
|
19
|
-
|
|
19
|
+
const { client, signer } = await resolveClient();
|
|
20
|
+
let { action, poolType, poolId } = args;
|
|
20
21
|
const { amount, slippage } = args;
|
|
22
|
+
// 1. Action Alias Mapping
|
|
23
|
+
if (action === "add" || action === "increase")
|
|
24
|
+
action = "deposit";
|
|
25
|
+
if (action === "remove" || action === "decrease")
|
|
26
|
+
action = "withdraw";
|
|
27
|
+
// 2. Smart Pool Resolution (Handles PoolId, Token Address, or Keywords)
|
|
28
|
+
poolId = await resolvePool(client, poolId);
|
|
21
29
|
let raw;
|
|
22
30
|
if (poolType === "QUOTE") {
|
|
23
31
|
raw = action === "deposit"
|
|
@@ -29,6 +37,9 @@ export const manageLiquidityTool = {
|
|
|
29
37
|
? await baseDeposit(poolId, amount, slippage, args.chainId)
|
|
30
38
|
: await baseWithdraw(poolId, amount, slippage, args.chainId);
|
|
31
39
|
}
|
|
40
|
+
if (!raw) {
|
|
41
|
+
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 pool_info.`);
|
|
42
|
+
}
|
|
32
43
|
const data = await finalizeMutationResult(raw, signer, "manage_liquidity");
|
|
33
44
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
34
45
|
}
|
|
@@ -43,7 +54,7 @@ export const getLpPriceTool = {
|
|
|
43
54
|
schema: {
|
|
44
55
|
poolType: z.enum(["BASE", "QUOTE"]).describe("'BASE' or 'QUOTE'"),
|
|
45
56
|
poolId: z.string().describe("Pool ID"),
|
|
46
|
-
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
57
|
+
chainId: z.coerce.number().int().positive().optional().describe("Optional chainId override"),
|
|
47
58
|
},
|
|
48
59
|
handler: async (args) => {
|
|
49
60
|
try {
|
package/dist/tools/marketInfo.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
-
import { getMarketDetail } from "../services/marketService.js";
|
|
3
|
+
import { getMarketDetail, resolvePool } from "../services/marketService.js";
|
|
4
4
|
import { getPoolInfo, getLiquidityInfo } from "../services/poolService.js";
|
|
5
5
|
export const getMarketDetailTool = {
|
|
6
6
|
name: "get_market_detail",
|
|
7
|
-
description: "Get detailed information for a specific trading pool (fee rates, open interest, etc.).",
|
|
7
|
+
description: "Get detailed information for a specific trading pool (fee rates, open interest, etc.). Supports PoolId, Token Address, or Keywords.",
|
|
8
8
|
schema: {
|
|
9
|
-
poolId: z.string().describe("Pool ID"),
|
|
9
|
+
poolId: z.string().describe("Pool ID, Token Address, or Keyword"),
|
|
10
10
|
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
11
11
|
},
|
|
12
12
|
handler: async (args) => {
|
|
13
13
|
try {
|
|
14
14
|
const { client } = await resolveClient();
|
|
15
|
+
const poolId = await resolvePool(client, args.poolId);
|
|
15
16
|
const chainId = args.chainId ?? getChainId();
|
|
16
|
-
const data = await getMarketDetail(client,
|
|
17
|
+
const data = await getMarketDetail(client, poolId, chainId);
|
|
18
|
+
if (!data)
|
|
19
|
+
throw new Error(`Market detail for ${poolId} not found.`);
|
|
17
20
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
18
21
|
}
|
|
19
22
|
catch (error) {
|
|
@@ -23,15 +26,19 @@ export const getMarketDetailTool = {
|
|
|
23
26
|
};
|
|
24
27
|
export const getPoolInfoTool = {
|
|
25
28
|
name: "get_pool_info",
|
|
26
|
-
description: "Get pool on-chain information (reserves, utilization, etc.).",
|
|
29
|
+
description: "Get pool on-chain information (reserves, utilization, etc.). Supports PoolId, Token Address, or Keywords.",
|
|
27
30
|
schema: {
|
|
28
|
-
poolId: z.string().describe("Pool ID"),
|
|
31
|
+
poolId: z.string().describe("Pool ID, Token Address, or Keyword"),
|
|
29
32
|
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
30
33
|
},
|
|
31
34
|
handler: async (args) => {
|
|
32
35
|
try {
|
|
36
|
+
const { client } = await resolveClient();
|
|
37
|
+
const poolId = await resolvePool(client, args.poolId);
|
|
33
38
|
const chainId = args.chainId ?? getChainId();
|
|
34
|
-
const data = await getPoolInfo(
|
|
39
|
+
const data = await getPoolInfo(poolId, chainId);
|
|
40
|
+
if (!data)
|
|
41
|
+
throw new Error(`Pool info for ${poolId} returned undefined. The pool may not exist on chainId ${chainId}.`);
|
|
35
42
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
36
43
|
}
|
|
37
44
|
catch (error) {
|
|
@@ -41,17 +48,20 @@ export const getPoolInfoTool = {
|
|
|
41
48
|
};
|
|
42
49
|
export const getLiquidityInfoTool = {
|
|
43
50
|
name: "get_liquidity_info",
|
|
44
|
-
description: "Get pool liquidity utilization and depth information.",
|
|
51
|
+
description: "Get pool liquidity utilization and depth information. Supports PoolId, Token Address, or Keywords.",
|
|
45
52
|
schema: {
|
|
46
|
-
poolId: z.string().describe("Pool ID"),
|
|
53
|
+
poolId: z.string().describe("Pool ID, Token Address, or Keyword"),
|
|
47
54
|
marketPrice: z.string().regex(/^\d+$/).describe("Current market price in 30-decimal raw units"),
|
|
48
55
|
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
49
56
|
},
|
|
50
57
|
handler: async (args) => {
|
|
51
58
|
try {
|
|
52
59
|
const { client } = await resolveClient();
|
|
60
|
+
const poolId = await resolvePool(client, args.poolId);
|
|
53
61
|
const chainId = args.chainId ?? getChainId();
|
|
54
|
-
const data = await getLiquidityInfo(client,
|
|
62
|
+
const data = await getLiquidityInfo(client, poolId, args.marketPrice, chainId);
|
|
63
|
+
if (!data)
|
|
64
|
+
throw new Error(`Liquidity info for ${poolId} not available.`);
|
|
55
65
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
56
66
|
}
|
|
57
67
|
catch (error) {
|
package/dist/tools/setTpSl.js
CHANGED
|
@@ -3,7 +3,7 @@ import { resolveClient } from "../auth/resolveClient.js";
|
|
|
3
3
|
import { setPositionTpSl } from "../services/tradeService.js";
|
|
4
4
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
5
5
|
import { mapDirection, mapTriggerType } from "../utils/mappings.js";
|
|
6
|
-
import {
|
|
6
|
+
import { SLIPPAGE_PCT_4DP_DESC } from "../utils/slippage.js";
|
|
7
7
|
export const setTpSlTool = {
|
|
8
8
|
name: "set_tp_sl",
|
|
9
9
|
description: "Create TP/SL order using SDK-native parameters.",
|
|
@@ -15,9 +15,7 @@ export const setTpSlTool = {
|
|
|
15
15
|
executionFeeToken: z.string().describe("Address of token to pay gas/execution fees."),
|
|
16
16
|
tpTriggerType: z.union([z.number(), z.string()]).optional().describe("0/NONE, 1/GTE, 2/LTE. e.g. 'GTE'."),
|
|
17
17
|
slTriggerType: z.union([z.number(), z.string()]).optional().describe("0/NONE, 1/GTE, 2/LTE. e.g. 'LTE'."),
|
|
18
|
-
slippagePct: z.coerce.string().
|
|
19
|
-
message: "slippagePct must be an integer in [0, 10000] with 4-decimal precision (1 = 0.01%).",
|
|
20
|
-
}).describe(`${SLIPPAGE_PCT_4DP_DESC}. Standard is 100 (1%).`),
|
|
18
|
+
slippagePct: z.coerce.string().default("50").describe(`${SLIPPAGE_PCT_4DP_DESC}. Default is 50 (0.5%).`),
|
|
21
19
|
tpPrice: z.coerce.string().optional().describe("Take Profit trigger price. e.g. '2.5' or 'raw:...' (30 decimals)."),
|
|
22
20
|
tpSize: z.coerce.string().optional().describe("TP size in base tokens. Use '0' to disable."),
|
|
23
21
|
slPrice: z.coerce.string().optional().describe("Stop Loss trigger price. e.g. '2.1' or 'raw:...' (30 decimals)."),
|
|
@@ -26,12 +24,20 @@ export const setTpSlTool = {
|
|
|
26
24
|
handler: async (args) => {
|
|
27
25
|
try {
|
|
28
26
|
const { client, address, signer } = await resolveClient();
|
|
27
|
+
const { chainId } = await resolveClient();
|
|
28
|
+
const poolId = args.poolId;
|
|
29
|
+
// Fetch pool detail to get quoteToken for execution fee
|
|
30
|
+
const poolResponse = await client.markets.getMarketDetail({ chainId, poolId });
|
|
31
|
+
const poolData = poolResponse?.data || (poolResponse?.marketId ? poolResponse : null);
|
|
32
|
+
if (!poolData)
|
|
33
|
+
throw new Error(`Could not find pool metadata for ID: ${poolId}`);
|
|
29
34
|
// Map inputs
|
|
30
35
|
const mappedArgs = {
|
|
31
36
|
...args,
|
|
32
37
|
direction: mapDirection(args.direction),
|
|
33
38
|
tpTriggerType: args.tpTriggerType !== undefined ? mapTriggerType(args.tpTriggerType) : undefined,
|
|
34
39
|
slTriggerType: args.slTriggerType !== undefined ? mapTriggerType(args.slTriggerType) : undefined,
|
|
40
|
+
executionFeeToken: poolData.quoteToken || args.executionFeeToken
|
|
35
41
|
};
|
|
36
42
|
const raw = await setPositionTpSl(client, address, mappedArgs);
|
|
37
43
|
const data = await finalizeMutationResult(raw, signer, "set_tp_sl");
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { decodeErrorSelector } from "./errors.js";
|
|
2
|
+
import { getChainId } from "../auth/resolveClient.js";
|
|
2
3
|
const TX_HASH_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
3
4
|
const TX_HASH_KEYS = new Set(["hash", "txHash", "transactionHash"]);
|
|
5
|
+
function getExplorerLink(txHash, chainId) {
|
|
6
|
+
if (chainId === 421614)
|
|
7
|
+
return `https://sepolia.arbiscan.io/tx/${txHash}`;
|
|
8
|
+
if (chainId === 59141)
|
|
9
|
+
return `https://sepolia.lineascan.build/tx/${txHash}`;
|
|
10
|
+
return txHash; // Fallback to just hash
|
|
11
|
+
}
|
|
4
12
|
function isObject(value) {
|
|
5
13
|
return !!value && typeof value === "object";
|
|
6
14
|
}
|
|
@@ -59,26 +67,35 @@ function assertSdkCode(result, actionName) {
|
|
|
59
67
|
export async function finalizeMutationResult(result, signer, actionName) {
|
|
60
68
|
assertSdkCode(result, actionName);
|
|
61
69
|
const txHash = findTxHashDeep(result);
|
|
70
|
+
const chainId = getChainId();
|
|
62
71
|
if (!txHash) {
|
|
63
72
|
return { result };
|
|
64
73
|
}
|
|
65
74
|
const provider = signer?.provider;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
let status = "submitted";
|
|
76
|
+
let receipt = null;
|
|
77
|
+
if (provider?.waitForTransaction) {
|
|
78
|
+
receipt = await provider.waitForTransaction(txHash, 1, 120000);
|
|
79
|
+
if (!receipt) {
|
|
80
|
+
throw new Error(`${actionName} failed: tx not confirmed within timeout (${txHash}).`);
|
|
81
|
+
}
|
|
82
|
+
if (receipt.status !== 1) {
|
|
83
|
+
throw new Error(`${actionName} failed on-chain: tx reverted (${txHash}).`);
|
|
84
|
+
}
|
|
85
|
+
status = "success";
|
|
75
86
|
}
|
|
76
87
|
return {
|
|
77
|
-
|
|
78
|
-
|
|
88
|
+
summary: {
|
|
89
|
+
action: actionName,
|
|
90
|
+
status: status,
|
|
91
|
+
txHash: txHash,
|
|
92
|
+
explorerUrl: getExplorerLink(txHash, chainId)
|
|
93
|
+
},
|
|
94
|
+
confirmation: receipt ? {
|
|
79
95
|
txHash,
|
|
80
96
|
blockNumber: receipt.blockNumber,
|
|
81
97
|
status: receipt.status,
|
|
82
|
-
},
|
|
98
|
+
} : { txHash, status: "submitted" },
|
|
99
|
+
raw: result
|
|
83
100
|
};
|
|
84
101
|
}
|