@michaleffffff/mcp-trading-server 3.0.4 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -6
- package/README.md +48 -414
- package/TOOL_EXAMPLES.md +63 -559
- 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/tradeService.js +33 -10
- package/dist/tools/accountInfo.js +1 -82
- package/dist/tools/accountTransfer.js +2 -2
- package/dist/tools/adjustMargin.js +1 -1
- package/dist/tools/cancelOrders.js +71 -0
- package/dist/tools/checkAccountReady.js +69 -0
- package/dist/tools/checkApproval.js +1 -1
- package/dist/tools/closeAllPositions.js +9 -7
- package/dist/tools/closePosition.js +1 -1
- 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 +41 -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 +83 -0
- package/dist/tools/getPositionsAll.js +80 -0
- package/dist/tools/{getMarketPrice.js → getPrice.js} +8 -5
- package/dist/tools/getUserTradingFeeRate.js +1 -1
- 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 +163 -0
- package/dist/tools/openPositionSimple.js +22 -5
- package/dist/tools/searchTools.js +35 -0
- package/dist/utils/mappings.js +10 -7
- package/package.json +1 -1
- 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
|
@@ -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"),
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
+
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
4
|
+
function normalizeDirectionInput(value) {
|
|
5
|
+
if (value === undefined || value === null || value === "")
|
|
6
|
+
return undefined;
|
|
7
|
+
if (typeof value === "number") {
|
|
8
|
+
if (value === 0 || value === 1)
|
|
9
|
+
return value;
|
|
10
|
+
throw new Error("direction must be LONG/SHORT or 0/1.");
|
|
11
|
+
}
|
|
12
|
+
const text = String(value).trim().toUpperCase();
|
|
13
|
+
if (text === "0" || text === "LONG" || text === "BUY")
|
|
14
|
+
return 0;
|
|
15
|
+
if (text === "1" || text === "SHORT" || text === "SELL")
|
|
16
|
+
return 1;
|
|
17
|
+
throw new Error("direction must be LONG/SHORT or 0/1.");
|
|
18
|
+
}
|
|
19
|
+
function isNonEmpty(value) {
|
|
20
|
+
return value !== undefined && value !== null && String(value).trim().length > 0;
|
|
21
|
+
}
|
|
22
|
+
function readSnapshotText(snapshot, keys) {
|
|
23
|
+
for (const key of keys) {
|
|
24
|
+
const value = snapshot?.[key];
|
|
25
|
+
if (isNonEmpty(value))
|
|
26
|
+
return String(value).trim();
|
|
27
|
+
}
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
function normalizeUnitsInput(text) {
|
|
31
|
+
const raw = String(text ?? "").trim();
|
|
32
|
+
if (!raw)
|
|
33
|
+
return "";
|
|
34
|
+
if (/^(raw|human):/i.test(raw))
|
|
35
|
+
return raw;
|
|
36
|
+
if (/^\d+$/.test(raw))
|
|
37
|
+
return `raw:${raw}`;
|
|
38
|
+
return raw;
|
|
39
|
+
}
|
|
40
|
+
async function findOrderSnapshot(client, address, chainId, orderId, poolId) {
|
|
41
|
+
const target = String(orderId).toLowerCase();
|
|
42
|
+
try {
|
|
43
|
+
const openRes = await client.order.getOrders(address);
|
|
44
|
+
const openOrders = Array.isArray(openRes?.data) ? openRes.data : [];
|
|
45
|
+
const found = openOrders.find((order) => String(order?.orderId ?? order?.id ?? "").toLowerCase() === target);
|
|
46
|
+
if (found)
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const historyQuery = { chainId, limit: 50, page: 1 };
|
|
53
|
+
if (poolId)
|
|
54
|
+
historyQuery.poolId = poolId;
|
|
55
|
+
const historyRes = await client.order.getOrderHistory(historyQuery, address);
|
|
56
|
+
const historyOrders = Array.isArray(historyRes?.data) ? historyRes.data : [];
|
|
57
|
+
return historyOrders.find((order) => String(order?.orderId ?? order?.id ?? "").toLowerCase() === target) ?? null;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export const manageTpSlTool = {
|
|
64
|
+
name: "manage_tp_sl",
|
|
65
|
+
description: "[TRADE] Set or update Take Profit (TP) and Stop Loss (SL) for a position.",
|
|
66
|
+
schema: {
|
|
67
|
+
poolId: z.string().describe("Pool ID"),
|
|
68
|
+
positionId: z.string().optional().describe("Position ID (required for creating TP/SL on a position)"),
|
|
69
|
+
orderId: z.string().optional().describe("Existing TP/SL Order ID to update (optional)"),
|
|
70
|
+
size: z.union([z.string(), z.number()]).optional().describe("Order size (required for update if cannot be auto-resolved from order snapshot)"),
|
|
71
|
+
price: z.union([z.string(), z.number()]).optional().describe("Order price (required for update if cannot be auto-resolved from order snapshot)"),
|
|
72
|
+
tpPrice: z.union([z.string(), z.number()]).optional().describe("New TP price"),
|
|
73
|
+
tpSize: z.union([z.string(), z.number()]).optional().describe("New TP size"),
|
|
74
|
+
slPrice: z.union([z.string(), z.number()]).optional().describe("New SL price"),
|
|
75
|
+
slSize: z.union([z.string(), z.number()]).optional().describe("New SL size"),
|
|
76
|
+
direction: z.union([z.enum(["LONG", "SHORT"]), z.number().int()]).optional().describe("Position direction (LONG/SHORT or 0/1; required for new TP/SL)"),
|
|
77
|
+
leverage: z.number().optional().describe("Position leverage"),
|
|
78
|
+
executionFeeToken: z.string().optional().describe("Fee token address"),
|
|
79
|
+
slippagePct: z.union([z.string(), z.number()]).optional().describe("Slippage in 4dp raw units. Default 100 (1%)."),
|
|
80
|
+
useOrderCollateral: z.boolean().optional().describe("Whether updateOrderTpSl should use order collateral (default true)."),
|
|
81
|
+
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
82
|
+
},
|
|
83
|
+
handler: async (args) => {
|
|
84
|
+
try {
|
|
85
|
+
const { client, address, signer } = await resolveClient();
|
|
86
|
+
const chainId = args.chainId ?? getChainId();
|
|
87
|
+
const direction = normalizeDirectionInput(args.direction);
|
|
88
|
+
const slippagePct = args.slippagePct ?? "100";
|
|
89
|
+
const { setPositionTpSl, updateOrderTpSl } = await import("../services/tradeService.js");
|
|
90
|
+
let raw;
|
|
91
|
+
if (args.orderId) {
|
|
92
|
+
// Update existing
|
|
93
|
+
const { getMarketDetail } = await import("../services/marketService.js");
|
|
94
|
+
const marketRes = await getMarketDetail(client, args.poolId, chainId);
|
|
95
|
+
const market = marketRes.data ?? marketRes;
|
|
96
|
+
const orderSnapshot = await findOrderSnapshot(client, address, chainId, args.orderId, args.poolId);
|
|
97
|
+
const snapshotSize = readSnapshotText(orderSnapshot, ["size", "orderSize", "positionSize"]);
|
|
98
|
+
const snapshotPrice = readSnapshotText(orderSnapshot, ["price", "orderPrice", "triggerPrice"]);
|
|
99
|
+
const snapshotTpPrice = readSnapshotText(orderSnapshot, ["tpPrice", "takeProfitPrice", "tpTriggerPrice"]);
|
|
100
|
+
const snapshotTpSize = readSnapshotText(orderSnapshot, ["tpSize", "takeProfitSize"]);
|
|
101
|
+
const snapshotSlPrice = readSnapshotText(orderSnapshot, ["slPrice", "stopLossPrice", "slTriggerPrice"]);
|
|
102
|
+
const snapshotSlSize = readSnapshotText(orderSnapshot, ["slSize", "stopLossSize"]);
|
|
103
|
+
const size = isNonEmpty(args.size) ? String(args.size) : snapshotSize;
|
|
104
|
+
const price = isNonEmpty(args.price) ? String(args.price) : snapshotPrice;
|
|
105
|
+
const tpPrice = isNonEmpty(args.tpPrice) ? String(args.tpPrice) : snapshotTpPrice;
|
|
106
|
+
const tpSize = isNonEmpty(args.tpSize) ? String(args.tpSize) : snapshotTpSize;
|
|
107
|
+
const slPrice = isNonEmpty(args.slPrice) ? String(args.slPrice) : snapshotSlPrice;
|
|
108
|
+
const slSize = isNonEmpty(args.slSize) ? String(args.slSize) : snapshotSlSize;
|
|
109
|
+
if (!size || !price) {
|
|
110
|
+
throw new Error("size and price are required for update. Provide them explicitly, or ensure orderId can be found via get_orders so they can be auto-resolved.");
|
|
111
|
+
}
|
|
112
|
+
if (!tpPrice && !slPrice) {
|
|
113
|
+
throw new Error("At least one of tpPrice or slPrice is required when updating TP/SL.");
|
|
114
|
+
}
|
|
115
|
+
if ((tpPrice && !tpSize) || (!tpPrice && tpSize)) {
|
|
116
|
+
throw new Error("TP update requires both tpPrice and tpSize (or resolvable existing TP fields).");
|
|
117
|
+
}
|
|
118
|
+
if ((slPrice && !slSize) || (!slPrice && slSize)) {
|
|
119
|
+
throw new Error("SL update requires both slPrice and slSize (or resolvable existing SL fields).");
|
|
120
|
+
}
|
|
121
|
+
raw = await updateOrderTpSl(client, address, {
|
|
122
|
+
orderId: args.orderId,
|
|
123
|
+
marketId: market.marketId,
|
|
124
|
+
poolId: args.poolId,
|
|
125
|
+
size: normalizeUnitsInput(size),
|
|
126
|
+
price: normalizeUnitsInput(price),
|
|
127
|
+
tpPrice: tpPrice ? normalizeUnitsInput(tpPrice) : "0",
|
|
128
|
+
tpSize: tpSize ? normalizeUnitsInput(tpSize) : "0",
|
|
129
|
+
slPrice: slPrice ? normalizeUnitsInput(slPrice) : "0",
|
|
130
|
+
slSize: slSize ? normalizeUnitsInput(slSize) : "0",
|
|
131
|
+
quoteToken: market.quoteToken,
|
|
132
|
+
useOrderCollateral: args.useOrderCollateral ?? true
|
|
133
|
+
}, chainId);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// Create new
|
|
137
|
+
if (direction === undefined || !args.leverage) {
|
|
138
|
+
throw new Error("direction and leverage are required when creating new TP/SL.");
|
|
139
|
+
}
|
|
140
|
+
if (!args.positionId) {
|
|
141
|
+
throw new Error("positionId is required when creating new TP/SL.");
|
|
142
|
+
}
|
|
143
|
+
raw = await setPositionTpSl(client, address, {
|
|
144
|
+
poolId: args.poolId,
|
|
145
|
+
positionId: args.positionId,
|
|
146
|
+
direction,
|
|
147
|
+
leverage: args.leverage,
|
|
148
|
+
executionFeeToken: args.executionFeeToken,
|
|
149
|
+
slippagePct,
|
|
150
|
+
tpPrice: args.tpPrice,
|
|
151
|
+
tpSize: args.tpSize,
|
|
152
|
+
slPrice: args.slPrice,
|
|
153
|
+
slSize: args.slSize
|
|
154
|
+
}, chainId);
|
|
155
|
+
}
|
|
156
|
+
const data = await finalizeMutationResult(raw, signer, "manage_tp_sl");
|
|
157
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
};
|
|
@@ -35,7 +35,7 @@ function pickMarketDetail(res) {
|
|
|
35
35
|
}
|
|
36
36
|
export const openPositionSimpleTool = {
|
|
37
37
|
name: "open_position_simple",
|
|
38
|
-
description: "High-level open position helper. Computes size/price/tradingFee and submits an increase order. Human units by default; use 'raw:' prefix for raw units.",
|
|
38
|
+
description: "[TRADE] High-level open position helper. Computes size/price/tradingFee and submits an increase order. Human units by default; use 'raw:' prefix for raw units.",
|
|
39
39
|
schema: {
|
|
40
40
|
poolId: z.string().optional().describe("Hex Pool ID. Provide either poolId or keyword."),
|
|
41
41
|
keyword: z.string().optional().describe('Recommended: Market keyword, e.g. "BTC", "ETH", "XRP".'),
|
|
@@ -56,7 +56,7 @@ export const openPositionSimpleTool = {
|
|
|
56
56
|
slippagePct: z.coerce
|
|
57
57
|
.string()
|
|
58
58
|
.optional()
|
|
59
|
-
.describe(`${SLIPPAGE_PCT_4DP_DESC} Default
|
|
59
|
+
.describe(`${SLIPPAGE_PCT_4DP_DESC} Default 50 (=0.5%).`),
|
|
60
60
|
postOnly: z.coerce.boolean().optional().describe("Post-only (default false)."),
|
|
61
61
|
executionFeeToken: z.string().optional().describe("Execution fee token address (default quoteToken)."),
|
|
62
62
|
assetClass: z.coerce.number().int().nonnegative().optional().describe("Fee query assetClass (default 1)."),
|
|
@@ -65,6 +65,10 @@ export const openPositionSimpleTool = {
|
|
|
65
65
|
.string()
|
|
66
66
|
.optional()
|
|
67
67
|
.describe("Trading fee. e.g. '0.2' USDC or 'raw:...'. Default: computed via getUserTradingFeeRate."),
|
|
68
|
+
tpPrice: z.coerce.string().optional().describe("Take Profit trigger price."),
|
|
69
|
+
tpSize: z.coerce.string().optional().describe("Take Profit size (base units). If omitted but tpPrice set, uses full position size."),
|
|
70
|
+
slPrice: z.coerce.string().optional().describe("Stop Loss trigger price."),
|
|
71
|
+
slSize: z.coerce.string().optional().describe("Stop Loss size (base units). If omitted but slPrice set, uses full position size."),
|
|
68
72
|
autoApprove: z.coerce.boolean().optional().describe("If true, auto-approve token spend (default false)."),
|
|
69
73
|
approveMax: z.coerce.boolean().optional().describe("If autoApprove, approve MaxUint256 (default false)."),
|
|
70
74
|
autoDeposit: z.coerce
|
|
@@ -118,7 +122,7 @@ export const openPositionSimpleTool = {
|
|
|
118
122
|
}
|
|
119
123
|
const orderType = mapOrderType(args.orderType ?? 0);
|
|
120
124
|
const postOnly = Boolean(args.postOnly ?? false);
|
|
121
|
-
const slippagePct = normalizeSlippagePct4dp(args.slippagePct ?? "
|
|
125
|
+
const slippagePct = normalizeSlippagePct4dp(args.slippagePct ?? "50");
|
|
122
126
|
const executionFeeToken = normalizeAddress(args.executionFeeToken || quoteToken, "executionFeeToken");
|
|
123
127
|
// 4) Determine reference price (30 decimals)
|
|
124
128
|
let price30;
|
|
@@ -222,6 +226,10 @@ export const openPositionSimpleTool = {
|
|
|
222
226
|
autoDeposit: Boolean(args.autoDeposit ?? false),
|
|
223
227
|
priceMeta,
|
|
224
228
|
sizeMeta,
|
|
229
|
+
tpPrice: args.tpPrice ? parseUserPrice30(args.tpPrice, "tpPrice") : null,
|
|
230
|
+
tpSize: args.tpSize ? parseUserUnits(args.tpSize, baseDecimals, "tpSize") : (args.tpPrice ? sizeRaw : null),
|
|
231
|
+
slPrice: args.slPrice ? parseUserPrice30(args.slPrice, "slPrice") : null,
|
|
232
|
+
slSize: args.slSize ? parseUserUnits(args.slSize, baseDecimals, "slSize") : (args.slPrice ? sizeRaw : null),
|
|
225
233
|
};
|
|
226
234
|
if (args.dryRun) {
|
|
227
235
|
return {
|
|
@@ -254,7 +262,7 @@ export const openPositionSimpleTool = {
|
|
|
254
262
|
}
|
|
255
263
|
}
|
|
256
264
|
// 8) Submit increase order using existing trade service
|
|
257
|
-
const
|
|
265
|
+
const openArgs = {
|
|
258
266
|
poolId,
|
|
259
267
|
positionId: "",
|
|
260
268
|
orderType,
|
|
@@ -271,7 +279,16 @@ export const openPositionSimpleTool = {
|
|
|
271
279
|
tradingFee: `raw:${String(tradingFeeRaw)}`,
|
|
272
280
|
marketId,
|
|
273
281
|
autoDeposit: Boolean(args.autoDeposit ?? false),
|
|
274
|
-
}
|
|
282
|
+
};
|
|
283
|
+
if (prep.tpPrice) {
|
|
284
|
+
openArgs.tpPrice = `raw:${prep.tpPrice}`;
|
|
285
|
+
openArgs.tpSize = `raw:${prep.tpSize}`;
|
|
286
|
+
}
|
|
287
|
+
if (prep.slPrice) {
|
|
288
|
+
openArgs.slPrice = `raw:${prep.slPrice}`;
|
|
289
|
+
openArgs.slSize = `raw:${prep.slSize}`;
|
|
290
|
+
}
|
|
291
|
+
const raw = await openPosition(client, address, openArgs);
|
|
275
292
|
const data = await finalizeMutationResult(raw, signer, "open_position_simple");
|
|
276
293
|
const txHash = data.confirmation?.txHash;
|
|
277
294
|
const verification = txHash ? await verifyTradeOutcome(client, address, poolId, txHash) : null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as allTools from "./index.js";
|
|
3
|
+
export const searchToolsTool = {
|
|
4
|
+
name: "search_tools",
|
|
5
|
+
description: "[UTILS] Search for available tools by keyword in their name or description.",
|
|
6
|
+
schema: {
|
|
7
|
+
keyword: z.string().describe("Keyword to search for in tool names or descriptions."),
|
|
8
|
+
},
|
|
9
|
+
handler: async (args) => {
|
|
10
|
+
try {
|
|
11
|
+
const keyword = args.keyword.toLowerCase();
|
|
12
|
+
const tools = Object.values(allTools);
|
|
13
|
+
const matches = tools
|
|
14
|
+
.filter(t => (t.name && t.name.toLowerCase().includes(keyword)) ||
|
|
15
|
+
(t.description && t.description.toLowerCase().includes(keyword)))
|
|
16
|
+
.map(t => ({
|
|
17
|
+
name: t.name,
|
|
18
|
+
description: t.description,
|
|
19
|
+
// We only return name and description to keep it concise
|
|
20
|
+
}));
|
|
21
|
+
if (matches.length === 0) {
|
|
22
|
+
return { content: [{ type: "text", text: `No tools found matching: ${args.keyword}` }] };
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
content: [{
|
|
26
|
+
type: "text",
|
|
27
|
+
text: JSON.stringify({ status: "success", count: matches.length, tools: matches }, null, 2)
|
|
28
|
+
}]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
package/dist/utils/mappings.js
CHANGED
|
@@ -50,13 +50,16 @@ export const getHistoryOrderStatusDesc = (status) => {
|
|
|
50
50
|
* MarketPoolState
|
|
51
51
|
*/
|
|
52
52
|
export const getMarketStateDesc = (state) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
case
|
|
58
|
-
case
|
|
59
|
-
|
|
53
|
+
if (state === undefined || state === null || state === "")
|
|
54
|
+
return "Unknown";
|
|
55
|
+
const s = Number(state);
|
|
56
|
+
switch (s) {
|
|
57
|
+
case 0: return "Created (Pending Setup)";
|
|
58
|
+
case 1: return "WaitOracle (Waiting for Price)";
|
|
59
|
+
case 2: return "Active (Tradable)";
|
|
60
|
+
case 3: return "PreDelisting (Closing Only)";
|
|
61
|
+
case 4: return "Delisted (Closed)";
|
|
62
|
+
default: return `Other(${s})`;
|
|
60
63
|
}
|
|
61
64
|
};
|
|
62
65
|
/**
|
package/package.json
CHANGED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
-
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
4
|
-
function normalizeOrderIds(input) {
|
|
5
|
-
if (Array.isArray(input)) {
|
|
6
|
-
return input.map((id) => String(id).trim()).filter(Boolean);
|
|
7
|
-
}
|
|
8
|
-
if (typeof input === "string") {
|
|
9
|
-
const text = input.trim();
|
|
10
|
-
if (!text)
|
|
11
|
-
return [];
|
|
12
|
-
// Support toolchains that serialize arrays as JSON strings.
|
|
13
|
-
if (text.startsWith("[") && text.endsWith("]")) {
|
|
14
|
-
try {
|
|
15
|
-
const parsed = JSON.parse(text);
|
|
16
|
-
if (Array.isArray(parsed)) {
|
|
17
|
-
return parsed.map((id) => String(id).trim()).filter(Boolean);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
// Fallback to comma/single parsing below.
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
if (text.includes(",")) {
|
|
25
|
-
return text.split(",").map((id) => id.trim()).filter(Boolean);
|
|
26
|
-
}
|
|
27
|
-
return [text];
|
|
28
|
-
}
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
export const cancelAllOrdersTool = {
|
|
32
|
-
name: "cancel_all_orders",
|
|
33
|
-
description: "Cancel open orders by orderIds. Accepts array, JSON-array string, comma-separated string, or single orderId.",
|
|
34
|
-
schema: {
|
|
35
|
-
orderIds: z
|
|
36
|
-
.union([z.array(z.string()).min(1), z.string().min(1)])
|
|
37
|
-
.describe("Order IDs to cancel. Supports array or string (single/comma/JSON-array)."),
|
|
38
|
-
},
|
|
39
|
-
handler: async (args) => {
|
|
40
|
-
try {
|
|
41
|
-
const { client, signer } = await resolveClient();
|
|
42
|
-
const chainId = getChainId();
|
|
43
|
-
const orderIds = normalizeOrderIds(args.orderIds);
|
|
44
|
-
if (orderIds.length === 0) {
|
|
45
|
-
throw new Error("orderIds is required and must include at least one non-empty order id.");
|
|
46
|
-
}
|
|
47
|
-
const raw = await client.order.cancelAllOrders(orderIds, chainId);
|
|
48
|
-
const result = await finalizeMutationResult(raw, signer, "cancel_all_orders");
|
|
49
|
-
return {
|
|
50
|
-
content: [{ type: "text", text: JSON.stringify({ status: "success", data: { cancelled: orderIds.length, orderIds, result } }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }],
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
-
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
4
|
-
export const cancelOrderTool = {
|
|
5
|
-
name: "cancel_order",
|
|
6
|
-
description: "Cancel an open order by its order ID.",
|
|
7
|
-
schema: {
|
|
8
|
-
orderId: z.string().describe("Order ID to cancel"),
|
|
9
|
-
},
|
|
10
|
-
handler: async (args) => {
|
|
11
|
-
try {
|
|
12
|
-
const { client, signer } = await resolveClient();
|
|
13
|
-
const chainId = getChainId();
|
|
14
|
-
const raw = await client.order.cancelOrder(args.orderId, chainId);
|
|
15
|
-
const data = await finalizeMutationResult(raw, signer, "cancel_order");
|
|
16
|
-
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
17
|
-
}
|
|
18
|
-
catch (error) {
|
|
19
|
-
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
-
export const getAccountVipInfoTool = {
|
|
4
|
-
name: "get_account_vip_info",
|
|
5
|
-
description: "Get account VIP/fee-tier information.",
|
|
6
|
-
schema: {
|
|
7
|
-
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
8
|
-
},
|
|
9
|
-
handler: async (args) => {
|
|
10
|
-
try {
|
|
11
|
-
const { client, address } = await resolveClient();
|
|
12
|
-
const chainId = args.chainId ?? getChainId();
|
|
13
|
-
const result = await client.account.getAccountVipInfo(chainId, address);
|
|
14
|
-
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
|
|
15
|
-
}
|
|
16
|
-
catch (error) {
|
|
17
|
-
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
-
const klineIntervalSchema = z.enum(["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "1M"]);
|
|
4
|
-
export const getKlineLatestBarTool = {
|
|
5
|
-
name: "get_kline_latest_bar",
|
|
6
|
-
description: "Get the latest single K-line/candlestick bar for a pool.",
|
|
7
|
-
schema: {
|
|
8
|
-
poolId: z.string().describe("Pool ID"),
|
|
9
|
-
interval: klineIntervalSchema.describe("K-line interval"),
|
|
10
|
-
},
|
|
11
|
-
handler: async (args) => {
|
|
12
|
-
try {
|
|
13
|
-
const { client } = await resolveClient();
|
|
14
|
-
const chainId = getChainId();
|
|
15
|
-
const result = await client.markets.getKlineLatestBar({
|
|
16
|
-
poolId: args.poolId,
|
|
17
|
-
chainId,
|
|
18
|
-
interval: args.interval,
|
|
19
|
-
limit: 1,
|
|
20
|
-
endTime: Date.now(),
|
|
21
|
-
});
|
|
22
|
-
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
-
import { getOraclePrice } from "../services/marketService.js";
|
|
4
|
-
export const getOraclePriceTool = {
|
|
5
|
-
name: "get_oracle_price",
|
|
6
|
-
description: "Get the current oracle price for a specific pool.",
|
|
7
|
-
schema: {
|
|
8
|
-
poolId: z.string().describe("Pool ID to get oracle price for"),
|
|
9
|
-
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
10
|
-
},
|
|
11
|
-
handler: async (args) => {
|
|
12
|
-
try {
|
|
13
|
-
const { client } = await resolveClient();
|
|
14
|
-
const chainId = args.chainId ?? getChainId();
|
|
15
|
-
const data = await getOraclePrice(client, args.poolId, chainId);
|
|
16
|
-
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
17
|
-
}
|
|
18
|
-
catch (error) {
|
|
19
|
-
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { resolveClient } from "../auth/resolveClient.js";
|
|
2
|
-
import { getPoolList } from "../services/marketService.js";
|
|
3
|
-
export const getPoolListTool = {
|
|
4
|
-
name: "get_pool_list",
|
|
5
|
-
description: "Get the complete list of all available tradable pools directly from the SDK.",
|
|
6
|
-
schema: {},
|
|
7
|
-
handler: async () => {
|
|
8
|
-
try {
|
|
9
|
-
const { client } = await resolveClient();
|
|
10
|
-
const result = await getPoolList(client);
|
|
11
|
-
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { resolveClient } from "../auth/resolveClient.js";
|
|
2
|
-
export const getPoolSymbolAllTool = {
|
|
3
|
-
name: "get_pool_symbol_all",
|
|
4
|
-
description: "Get symbol/icon metadata for all pools.",
|
|
5
|
-
schema: {},
|
|
6
|
-
handler: async () => {
|
|
7
|
-
try {
|
|
8
|
-
const { client } = await resolveClient();
|
|
9
|
-
const result = await client.markets.getPoolSymbolAll();
|
|
10
|
-
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
|
|
11
|
-
}
|
|
12
|
-
catch (error) {
|
|
13
|
-
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
};
|