@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
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
|
|
3
|
-
import { getOraclePrice } from "../services/marketService.js";
|
|
3
|
+
import { getOraclePrice, resolvePool } from "../services/marketService.js";
|
|
4
4
|
import { ensureUnits } from "../utils/units.js";
|
|
5
5
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
6
6
|
import { normalizeSlippagePct4dpFlexible, SLIPPAGE_PCT_4DP_DESC } from "../utils/slippage.js";
|
|
7
7
|
export const closeAllPositionsTool = {
|
|
8
8
|
name: "close_all_positions",
|
|
9
|
-
description: "Emergency: close ALL open positions in a pool at once. Use for risk management.",
|
|
9
|
+
description: "[TRADE] Emergency: close ALL open positions in a pool at once. Use for risk management.",
|
|
10
10
|
schema: {
|
|
11
|
-
poolId: z.string().describe("Pool ID
|
|
11
|
+
poolId: z.string().optional().describe("Pool ID or keyword."),
|
|
12
|
+
keyword: z.string().optional().describe("Market keyword, e.g. 'BTC'."),
|
|
12
13
|
slippagePct: z.union([z.string(), z.number()]).optional().describe(`${SLIPPAGE_PCT_4DP_DESC}. Also supports human percent format like "1.0" or "1%".`),
|
|
13
14
|
},
|
|
14
15
|
handler: async (args) => {
|
|
15
16
|
try {
|
|
16
17
|
const { client, address, signer } = await resolveClient();
|
|
17
18
|
const chainId = getChainId();
|
|
19
|
+
const poolId = await resolvePool(client, args.poolId, args.keyword);
|
|
18
20
|
// 1) 先获取该池的所有持仓
|
|
19
21
|
const posResult = await client.position.listPositions(address);
|
|
20
22
|
const positions = posResult?.data || posResult || [];
|
|
21
23
|
// 过滤出指定 pool 的仓位
|
|
22
24
|
const poolPositions = Array.isArray(positions)
|
|
23
|
-
? positions.filter((p) => p.poolId ===
|
|
25
|
+
? positions.filter((p) => p.poolId === poolId || p.pool_id === poolId)
|
|
24
26
|
: [];
|
|
25
27
|
if (poolPositions.length === 0) {
|
|
26
28
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: { message: "No open positions in this pool.", closed: 0 } }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
@@ -28,14 +30,14 @@ export const closeAllPositionsTool = {
|
|
|
28
30
|
// 2) 为每个仓位构建平仓参数
|
|
29
31
|
const slippagePct = normalizeSlippagePct4dpFlexible(args.slippagePct ?? "200");
|
|
30
32
|
// 3) We need actual oracle prices to avoid Revert 0x613970e0 (InvalidParameter)
|
|
31
|
-
const oraclePriceReq = await getOraclePrice(client,
|
|
33
|
+
const oraclePriceReq = await getOraclePrice(client, poolId).catch(() => null);
|
|
32
34
|
let fallbackPrice = "0";
|
|
33
35
|
if (oraclePriceReq && oraclePriceReq.price) {
|
|
34
36
|
fallbackPrice = oraclePriceReq.price.toString();
|
|
35
37
|
}
|
|
36
38
|
else {
|
|
37
39
|
const { getMarketPrice } = await import("../services/marketService.js");
|
|
38
|
-
const marketData = await getMarketPrice(client,
|
|
40
|
+
const marketData = await getMarketPrice(client, poolId).catch(() => null);
|
|
39
41
|
if (marketData && marketData.price) {
|
|
40
42
|
fallbackPrice = marketData.price.toString();
|
|
41
43
|
}
|
|
@@ -50,7 +52,7 @@ export const closeAllPositionsTool = {
|
|
|
50
52
|
return {
|
|
51
53
|
chainId,
|
|
52
54
|
address,
|
|
53
|
-
poolId:
|
|
55
|
+
poolId: poolId,
|
|
54
56
|
positionId: pos.positionId || pos.position_id || pos.id,
|
|
55
57
|
orderType: 0, // MARKET
|
|
56
58
|
triggerType: 0, // NONE
|
|
@@ -24,7 +24,7 @@ function readRawPositionField(position, primary, fallback) {
|
|
|
24
24
|
}
|
|
25
25
|
export const closePositionTool = {
|
|
26
26
|
name: "close_position",
|
|
27
|
-
description: "Create a decrease order using SDK-native parameters.",
|
|
27
|
+
description: "[TRADE] Create a decrease order (close or reduce position) using SDK-native parameters.",
|
|
28
28
|
schema: {
|
|
29
29
|
poolId: z.string().describe("Pool ID"),
|
|
30
30
|
positionId: z.string().describe("Position ID to close"),
|
|
@@ -4,7 +4,7 @@ import { resolveClient } from "../auth/resolveClient.js";
|
|
|
4
4
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
5
5
|
export const createPerpMarketTool = {
|
|
6
6
|
name: "create_perp_market",
|
|
7
|
-
description: "Create a new perpetual contract pool on MYX. IMPORTANT: marketId cannot be randomly generated. It must be a valid 66-character config hash (0x...) tied to a supported quote token (like USDC). Use
|
|
7
|
+
description: "[LIQUIDITY] Create a new perpetual contract pool on MYX. IMPORTANT: marketId cannot be randomly generated. It must be a valid 66-character config hash (0x...) tied to a supported quote token (like USDC). Use get_pool_metadata (after find_pool/list_pools) to fetch an existing marketId if you don't have a specific newly allocated one.",
|
|
8
8
|
schema: {
|
|
9
9
|
baseToken: z.string().describe("Base token contract address (e.g., 0xb40aaadc43...)"),
|
|
10
10
|
marketId: z.string().describe("MUST be a valid 66-char config hash (e.g., existing USDC marketId: 0x7f6727d8026fd2c87ccc745846c83cd0b68e886c73e1e05a54a675bcadd8adb6). Do NOT generate randomly."),
|
|
@@ -11,12 +11,12 @@ const POSITION_ID_RE = /^$|^0x[0-9a-fA-F]{64}$/;
|
|
|
11
11
|
const ZERO_POSITION_ID_RE = /^0x0{64}$/i;
|
|
12
12
|
export const executeTradeTool = {
|
|
13
13
|
name: "execute_trade",
|
|
14
|
-
description: "Create an increase order using SDK-native parameters.",
|
|
14
|
+
description: "[TRADE] Create an increase order (open or add to position) using SDK-native parameters.",
|
|
15
15
|
schema: {
|
|
16
|
-
poolId: z.string().describe("Hex Pool ID, e.g. '0x14a19...'. Get via
|
|
17
|
-
positionId: z.string().refine((value) => POSITION_ID_RE.test(value), {
|
|
16
|
+
poolId: z.string().describe("Hex Pool ID, e.g. '0x14a19...'. Get via list_pools."),
|
|
17
|
+
positionId: z.string().optional().refine((value) => !value || POSITION_ID_RE.test(value), {
|
|
18
18
|
message: "positionId must be empty string for new position, or a bytes32 hex string.",
|
|
19
|
-
}).describe("Position ID: Use empty string '' for NEW positions, or valid hex for INCREASING existing ones. 0x000..00 is auto-treated as NEW."),
|
|
19
|
+
}).describe("Position ID: Use empty string '' for NEW positions, or valid hex for INCREASING existing ones. 0x000..00 is auto-treated as NEW. Default is empty string."),
|
|
20
20
|
orderType: z.union([z.number(), z.string()]).describe("Market/Limit/Stop. e.g. 0 or 'MARKET'."),
|
|
21
21
|
triggerType: z.union([z.number(), z.string()]).optional().describe("0=None (Market), 1=GTE, 2=LTE. e.g. 'GTE'."),
|
|
22
22
|
direction: z.union([z.number(), z.string()]).describe("0/LONG/BUY or 1/SHORT/SELL."),
|
|
@@ -26,7 +26,7 @@ export const executeTradeTool = {
|
|
|
26
26
|
timeInForce: z.coerce.number().int().describe("0=GTC, 1=IOC, 2=FOK"),
|
|
27
27
|
postOnly: z.coerce.boolean().describe("If true, order only executes as Maker."),
|
|
28
28
|
slippagePct: z.coerce.string().default("50").describe(`${SLIPPAGE_PCT_4DP_DESC}. Default is 50 (0.5%).`),
|
|
29
|
-
executionFeeToken: z.string().describe("Address of token to pay gas/execution fees (typically USDC)."),
|
|
29
|
+
executionFeeToken: z.string().optional().describe("Address of token to pay gas/execution fees (typically USDC). Default is pool quoteToken."),
|
|
30
30
|
leverage: z.coerce.number().positive().describe("Leverage multiplier, e.g., 10 for 10x."),
|
|
31
31
|
tpSize: z.union([z.string(), z.number()]).optional().describe("Take Profit size. Use '0' to disable."),
|
|
32
32
|
tpPrice: z.union([z.string(), z.number()]).optional().describe("Take Profit trigger price."),
|
|
@@ -35,7 +35,7 @@ export const executeTradeTool = {
|
|
|
35
35
|
tradingFee: z.union([z.string(), z.number()]).optional().describe("Trading fee in quote token units. Supports human/raw prefix. Optional: auto-computed via get_user_trading_fee_rate."),
|
|
36
36
|
assetClass: z.coerce.number().int().nonnegative().optional().describe("Optional fee lookup assetClass (default from pool config or 1)."),
|
|
37
37
|
riskTier: z.coerce.number().int().nonnegative().optional().describe("Optional fee lookup riskTier (default from pool config or 1)."),
|
|
38
|
-
marketId: z.string().describe("Specific Market Config Hash. Fetch via
|
|
38
|
+
marketId: z.string().describe("Specific Market Config Hash. Fetch via get_pool_metadata (resolve poolId first with find_pool/list_pools)."),
|
|
39
39
|
},
|
|
40
40
|
handler: async (args) => {
|
|
41
41
|
try {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveClient } from "../auth/resolveClient.js";
|
|
3
|
+
import { searchMarket } from "../services/marketService.js";
|
|
4
|
+
export const findPoolTool = {
|
|
5
|
+
name: "find_pool",
|
|
6
|
+
description: "[MARKET] Search for pools by keyword or symbol. Returns matched market metadata.",
|
|
7
|
+
schema: {
|
|
8
|
+
keyword: z.string().describe("Search keyword or symbol, e.g. 'BTC', 'ETH/USDC'"),
|
|
9
|
+
limit: z.number().int().positive().optional().describe("Max search results (default 10)"),
|
|
10
|
+
},
|
|
11
|
+
handler: async (args) => {
|
|
12
|
+
try {
|
|
13
|
+
const { client } = await resolveClient();
|
|
14
|
+
const results = await searchMarket(client, args.keyword, args.limit ?? 10);
|
|
15
|
+
return {
|
|
16
|
+
content: [{
|
|
17
|
+
type: "text",
|
|
18
|
+
text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)
|
|
19
|
+
}]
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
+
export const getAccountSnapshotTool = {
|
|
4
|
+
name: "get_account_snapshot",
|
|
5
|
+
description: "[ACCOUNT] Get unified account snapshot (balances, trading metrics, and VIP tier).",
|
|
6
|
+
schema: {
|
|
7
|
+
poolId: z.string().optional().describe("Pool ID for detailed trading-account metrics."),
|
|
8
|
+
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
9
|
+
},
|
|
10
|
+
handler: async (args) => {
|
|
11
|
+
try {
|
|
12
|
+
const { client, address } = await resolveClient();
|
|
13
|
+
const chainId = args.chainId ?? getChainId();
|
|
14
|
+
const { getBalances, getMarginBalance } = await import("../services/balanceService.js");
|
|
15
|
+
const [vipRes, walletRes] = await Promise.all([
|
|
16
|
+
client.account.getAccountVipInfo(chainId, address).catch(() => null),
|
|
17
|
+
getBalances(client, address, chainId).catch(() => null)
|
|
18
|
+
]);
|
|
19
|
+
let tradingAccount = null;
|
|
20
|
+
if (args.poolId) {
|
|
21
|
+
const [info, margin] = await Promise.all([
|
|
22
|
+
client.account.getAccountInfo(chainId, address, args.poolId).catch(() => null),
|
|
23
|
+
getMarginBalance(client, address, args.poolId, chainId).catch(() => null)
|
|
24
|
+
]);
|
|
25
|
+
tradingAccount = {
|
|
26
|
+
info: info?.data || info,
|
|
27
|
+
margin: margin?.data || margin
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const results = {
|
|
31
|
+
wallet: walletRes?.data || walletRes,
|
|
32
|
+
tradingAccount,
|
|
33
|
+
vip: vipRes?.data || vipRes,
|
|
34
|
+
};
|
|
35
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
2
|
+
import { getPoolList } from "../services/marketService.js";
|
|
2
3
|
export const getAllTickersTool = {
|
|
3
4
|
name: "get_all_tickers",
|
|
4
|
-
description: "Get ticker snapshots for all markets.",
|
|
5
|
+
description: "[MARKET] Get ticker snapshots for all markets.",
|
|
5
6
|
schema: {},
|
|
6
7
|
handler: async () => {
|
|
7
8
|
try {
|
|
@@ -13,9 +14,9 @@ export const getAllTickersTool = {
|
|
|
13
14
|
catch {
|
|
14
15
|
// Fallback for networks/environments where getAllTickers endpoint is unavailable.
|
|
15
16
|
const chainId = getChainId();
|
|
16
|
-
const poolList = await
|
|
17
|
-
const pools = Array.isArray(poolList?.data) ? poolList.data : [];
|
|
18
|
-
const poolIds = pools.map((p) => p
|
|
17
|
+
const poolList = await getPoolList(client, chainId);
|
|
18
|
+
const pools = Array.isArray(poolList?.data) ? poolList.data : (Array.isArray(poolList) ? poolList : []);
|
|
19
|
+
const poolIds = pools.map((p) => p?.poolId ?? p?.pool_id).filter((id) => !!id);
|
|
19
20
|
if (poolIds.length === 0) {
|
|
20
21
|
throw new Error("Failed to fetch all tickers and no pools were available for fallback query.");
|
|
21
22
|
}
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
3
|
export const getBaseDetailTool = {
|
|
4
4
|
name: "get_base_detail",
|
|
5
|
-
description: "Get base token details.",
|
|
5
|
+
description: "[MARKET] Get base token details.",
|
|
6
6
|
schema: {
|
|
7
7
|
baseAddress: z.string().describe("Base token address"),
|
|
8
8
|
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
package/dist/tools/getKline.js
CHANGED
|
@@ -3,18 +3,21 @@ import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
|
3
3
|
const klineIntervalSchema = z.enum(["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "1M"]);
|
|
4
4
|
export const getKlineTool = {
|
|
5
5
|
name: "get_kline",
|
|
6
|
-
description: "Get K-line / candlestick data for a pool.
|
|
6
|
+
description: "[MARKET] Get K-line / candlestick data for a pool. Supports PoolId or Keyword. Set limit=1 for the latest bar.",
|
|
7
7
|
schema: {
|
|
8
|
-
poolId: z.string().describe("Pool ID"),
|
|
8
|
+
poolId: z.string().optional().describe("Pool ID"),
|
|
9
|
+
keyword: z.string().optional().describe("Market keyword (e.g. 'BTC')"),
|
|
9
10
|
interval: klineIntervalSchema.describe("K-line interval"),
|
|
10
|
-
limit: z.number().int().positive().optional().describe("Number of bars (default 100)"),
|
|
11
|
+
limit: z.number().int().positive().optional().describe("Number of bars (default 100). Set to 1 for the latest price bar."),
|
|
11
12
|
},
|
|
12
13
|
handler: async (args) => {
|
|
13
14
|
try {
|
|
14
15
|
const { client } = await resolveClient();
|
|
15
16
|
const chainId = getChainId();
|
|
17
|
+
const { resolvePool } = await import("../services/marketService.js");
|
|
18
|
+
const poolId = await resolvePool(client, args.poolId, args.keyword);
|
|
16
19
|
const result = await client.markets.getKlineList({
|
|
17
|
-
poolId
|
|
20
|
+
poolId,
|
|
18
21
|
chainId,
|
|
19
22
|
interval: args.interval,
|
|
20
23
|
limit: args.limit ?? 100,
|
|
@@ -3,6 +3,7 @@ import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
|
3
3
|
import { COMMON_LP_AMOUNT_DECIMALS } from "@myx-trade/sdk";
|
|
4
4
|
import { Contract, formatUnits } from "ethers";
|
|
5
5
|
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
6
|
+
import { getPoolList } from "../services/marketService.js";
|
|
6
7
|
const ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
|
|
7
8
|
const ERC20_BALANCE_ABI = ["function balanceOf(address owner) view returns (uint256)"];
|
|
8
9
|
function collectRows(input) {
|
|
@@ -130,7 +131,7 @@ async function readErc20Balance(provider, tokenAddress, holder) {
|
|
|
130
131
|
}
|
|
131
132
|
export const getMyLpHoldingsTool = {
|
|
132
133
|
name: "get_my_lp_holdings",
|
|
133
|
-
description: "List your LP holdings across pools on the current chain by reading base/quote LP token balances. Includes standardized LP asset names: base LP `mBASE.QUOTE`, quote LP `mQUOTE.BASE`.",
|
|
134
|
+
description: "[ACCOUNT] List your LP holdings across pools on the current chain by reading base/quote LP token balances. Includes standardized LP asset names: base LP `mBASE.QUOTE`, quote LP `mQUOTE.BASE`.",
|
|
134
135
|
schema: {
|
|
135
136
|
includeZero: z.coerce.boolean().optional().describe("If true, include pools with zero LP balances (default false)."),
|
|
136
137
|
poolIds: z.union([z.array(z.string()).min(1), z.string().min(1)]).optional().describe("Optional poolId filter. Supports array, JSON-array string, comma string, or single poolId."),
|
|
@@ -148,7 +149,7 @@ export const getMyLpHoldingsTool = {
|
|
|
148
149
|
const poolIdsFilter = normalizePoolIdsInput(args.poolIds);
|
|
149
150
|
const filterSet = new Set(poolIdsFilter.map((item) => item.toLowerCase()));
|
|
150
151
|
const maxPools = Number.isFinite(Number(args.maxPools)) ? Math.floor(Number(args.maxPools)) : null;
|
|
151
|
-
const poolListRes = await
|
|
152
|
+
const poolListRes = await getPoolList(client, chainId);
|
|
152
153
|
const rows = collectRows(poolListRes?.data ?? poolListRes);
|
|
153
154
|
const deduped = new Map();
|
|
154
155
|
for (const row of rows) {
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
3
|
export const getNetworkFeeTool = {
|
|
4
4
|
name: "get_network_fee",
|
|
5
|
-
description: "Estimate network fee requirements for a market.",
|
|
5
|
+
description: "[TRADE] Estimate network fee requirements for a market.",
|
|
6
6
|
schema: {
|
|
7
7
|
marketId: z.string().describe("Market ID"),
|
|
8
8
|
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
+
import { getOrderTypeDesc, getOrderStatusDesc, getDirectionDesc, getHistoryOrderStatusDesc, getExecTypeDesc } from "../utils/mappings.js";
|
|
4
|
+
export const getOrdersTool = {
|
|
5
|
+
name: "get_orders",
|
|
6
|
+
description: "[ACCOUNT] Get orders (open or history) with optional filters.",
|
|
7
|
+
schema: {
|
|
8
|
+
status: z.enum(["OPEN", "HISTORY", "ALL"]).default("OPEN").describe("Filter by status: 'OPEN' (default), 'HISTORY', or 'ALL'"),
|
|
9
|
+
poolId: z.string().optional().describe("Filter by pool ID"),
|
|
10
|
+
limit: z.number().int().positive().optional().describe("Results per page (default 20)"),
|
|
11
|
+
},
|
|
12
|
+
handler: async (args) => {
|
|
13
|
+
try {
|
|
14
|
+
const { client, address } = await resolveClient();
|
|
15
|
+
const chainId = getChainId();
|
|
16
|
+
const results = {};
|
|
17
|
+
if (args.status === "OPEN" || args.status === "ALL") {
|
|
18
|
+
const openRes = await client.order.getOrders(address);
|
|
19
|
+
results.open = (openRes?.data || []).map((order) => ({
|
|
20
|
+
...order,
|
|
21
|
+
orderTypeDesc: getOrderTypeDesc(order.orderType),
|
|
22
|
+
statusDesc: getOrderStatusDesc(order.status),
|
|
23
|
+
directionDesc: getDirectionDesc(order.direction)
|
|
24
|
+
}));
|
|
25
|
+
if (args.poolId) {
|
|
26
|
+
results.open = results.open.filter((o) => String(o.poolId).toLowerCase() === args.poolId.toLowerCase());
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (args.status === "HISTORY" || args.status === "ALL") {
|
|
30
|
+
const query = { chainId, poolId: args.poolId, limit: args.limit ?? 20 };
|
|
31
|
+
const historyRes = await client.order.getOrderHistory(query, address);
|
|
32
|
+
results.history = (historyRes?.data || []).map((order) => ({
|
|
33
|
+
...order,
|
|
34
|
+
orderTypeDesc: getOrderTypeDesc(order.orderType),
|
|
35
|
+
orderStatusDesc: getHistoryOrderStatusDesc(order.orderStatus),
|
|
36
|
+
directionDesc: getDirectionDesc(order.direction),
|
|
37
|
+
execTypeDesc: getExecTypeDesc(order.execType)
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
+
import { getMarketDetail, resolvePool } from "../services/marketService.js";
|
|
4
|
+
import { getPoolInfo, getLiquidityInfo } from "../services/poolService.js";
|
|
5
|
+
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
6
|
+
import { parseUserPrice30 } from "../utils/units.js";
|
|
7
|
+
const INTEGER_RE = /^\d+$/;
|
|
8
|
+
const DECIMAL_RE = /^\d+(\.\d+)?$/;
|
|
9
|
+
function normalizeMarketPrice30Input(value) {
|
|
10
|
+
const text = String(value ?? "").trim();
|
|
11
|
+
if (!text)
|
|
12
|
+
return "";
|
|
13
|
+
if (INTEGER_RE.test(text))
|
|
14
|
+
return text;
|
|
15
|
+
if (!DECIMAL_RE.test(text) && !/^raw:/i.test(text) && !/^human:/i.test(text)) {
|
|
16
|
+
throw new Error("marketPrice must be numeric, or prefixed with raw:/human:.");
|
|
17
|
+
}
|
|
18
|
+
return parseUserPrice30(text, "marketPrice");
|
|
19
|
+
}
|
|
20
|
+
export const getPoolMetadataTool = {
|
|
21
|
+
name: "get_pool_metadata",
|
|
22
|
+
description: "[MARKET] Get comprehensive metadata for a pool (market detail, on-chain info, liquidity, and limits).",
|
|
23
|
+
schema: {
|
|
24
|
+
poolId: z.string().optional().describe("Pool ID, Token Address, or Keyword"),
|
|
25
|
+
keyword: z.string().optional().describe("Market keyword (e.g. 'BTC')"),
|
|
26
|
+
includeLiquidity: z.boolean().default(false).describe("Whether to include liquidity depth (requires marketPrice)"),
|
|
27
|
+
marketPrice: z.union([z.string(), z.number()]).optional().describe("Current price (required if includeLiquidity is true)"),
|
|
28
|
+
includeConfig: z.boolean().default(false).describe("Whether to include pool level configuration/limits"),
|
|
29
|
+
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
30
|
+
},
|
|
31
|
+
handler: async (args) => {
|
|
32
|
+
try {
|
|
33
|
+
const { client } = await resolveClient();
|
|
34
|
+
const chainId = args.chainId ?? getChainId();
|
|
35
|
+
const poolId = await resolvePool(client, args.poolId, args.keyword, chainId);
|
|
36
|
+
const results = { poolId, chainId };
|
|
37
|
+
const errors = [];
|
|
38
|
+
// 1. Market Detail (Fee rates, OI, etc.)
|
|
39
|
+
try {
|
|
40
|
+
results.marketDetail = await getMarketDetail(client, poolId, chainId);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
errors.push(`marketDetail: ${extractErrorMessage(err)}`);
|
|
44
|
+
}
|
|
45
|
+
// 2. Pool Info (Reserves, Utilization)
|
|
46
|
+
try {
|
|
47
|
+
results.poolInfo = await getPoolInfo(poolId, chainId, client);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
errors.push(`poolInfo: ${extractErrorMessage(err)}`);
|
|
51
|
+
}
|
|
52
|
+
// 3. Optional: Liquidity Info
|
|
53
|
+
if (args.includeLiquidity) {
|
|
54
|
+
try {
|
|
55
|
+
const price = normalizeMarketPrice30Input(args.marketPrice);
|
|
56
|
+
if (!price)
|
|
57
|
+
throw new Error("marketPrice is required for liquidity info.");
|
|
58
|
+
results.liquidityInfo = await getLiquidityInfo(client, poolId, price, chainId);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
errors.push(`liquidityInfo: ${extractErrorMessage(err)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// 4. Optional: Config / Limits
|
|
65
|
+
if (args.includeConfig) {
|
|
66
|
+
try {
|
|
67
|
+
const { getPoolLevelConfig } = await import("../services/marketService.js");
|
|
68
|
+
results.levelConfig = await getPoolLevelConfig(client, poolId, chainId);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
errors.push(`levelConfig: ${extractErrorMessage(err)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (errors.length > 0) {
|
|
75
|
+
results.warnings = errors;
|
|
76
|
+
}
|
|
77
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return { content: [{ type: "text", text: `Error: ${extractErrorMessage(error)}` }], isError: true };
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
+
import { getPositions } from "../services/positionService.js";
|
|
4
|
+
import { getDirectionDesc } from "../utils/mappings.js";
|
|
5
|
+
export const getPositionsAllTool = {
|
|
6
|
+
name: "get_positions_all",
|
|
7
|
+
description: "[ACCOUNT] Get positions (open or history) with optional filters and ROI/PnL metrics.",
|
|
8
|
+
schema: {
|
|
9
|
+
status: z.enum(["OPEN", "HISTORY", "ALL"]).default("OPEN").describe("Filter by status: 'OPEN' (default), 'HISTORY', or 'ALL'"),
|
|
10
|
+
poolId: z.string().optional().describe("Filter by pool ID"),
|
|
11
|
+
limit: z.number().int().positive().optional().describe("Results per page (default 20, for history)"),
|
|
12
|
+
},
|
|
13
|
+
handler: async (args) => {
|
|
14
|
+
try {
|
|
15
|
+
const { client, address } = await resolveClient();
|
|
16
|
+
const chainId = getChainId();
|
|
17
|
+
const results = {};
|
|
18
|
+
if (args.status === "OPEN" || args.status === "ALL") {
|
|
19
|
+
const data = await getPositions(client, address);
|
|
20
|
+
const positions = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
|
|
21
|
+
// Enhance open positions with metrics
|
|
22
|
+
const filtered = args.poolId ? positions.filter((p) => String(p.poolId).toLowerCase() === args.poolId.toLowerCase()) : positions;
|
|
23
|
+
if (filtered.length > 0) {
|
|
24
|
+
const poolIds = [...new Set(filtered.map((p) => p.poolId))];
|
|
25
|
+
const [tickersRes, configs] = await Promise.all([
|
|
26
|
+
client.markets.getTickerList({ chainId, poolIds }),
|
|
27
|
+
Promise.all(poolIds.map(async (pid) => {
|
|
28
|
+
try {
|
|
29
|
+
const { getPoolLevelConfig } = await import("../services/marketService.js");
|
|
30
|
+
const res = await getPoolLevelConfig(client, pid, chainId);
|
|
31
|
+
return { poolId: pid, config: res?.levelConfig || res?.data?.levelConfig || res };
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return { poolId: pid, config: null };
|
|
35
|
+
}
|
|
36
|
+
}))
|
|
37
|
+
]);
|
|
38
|
+
const tickers = Array.isArray(tickersRes) ? tickersRes : (tickersRes?.data ?? []);
|
|
39
|
+
results.open = filtered.map((pos) => {
|
|
40
|
+
const ticker = tickers.find((t) => t.poolId === pos.poolId);
|
|
41
|
+
const currentPrice = Number(ticker?.price || 0);
|
|
42
|
+
const entryPrice = Number(pos.entryPrice || 0);
|
|
43
|
+
const size = Number(pos.size || 0);
|
|
44
|
+
const collateral = Number(pos.collateralAmount || 0);
|
|
45
|
+
const direction = pos.direction;
|
|
46
|
+
const mm = configs.find(c => c.poolId === pos.poolId)?.config?.maintainCollateralRate || 0.02;
|
|
47
|
+
let estimatedPnl = direction === 0 ? (currentPrice - entryPrice) * size : (entryPrice - currentPrice) * size;
|
|
48
|
+
const roi = collateral > 0 ? (estimatedPnl / collateral) * 100 : 0;
|
|
49
|
+
let liqPrice = size > 0 ? (direction === 0 ? (entryPrice * size - collateral) / (size * (1 - mm)) : (entryPrice * size + collateral) / (size * (1 + mm))) : 0;
|
|
50
|
+
if (liqPrice < 0)
|
|
51
|
+
liqPrice = 0;
|
|
52
|
+
return {
|
|
53
|
+
...pos,
|
|
54
|
+
directionDesc: getDirectionDesc(pos.direction),
|
|
55
|
+
currentPrice: currentPrice.toString(),
|
|
56
|
+
estimatedPnl: estimatedPnl.toFixed(4),
|
|
57
|
+
roi: roi.toFixed(2) + "%",
|
|
58
|
+
liquidationPrice: liqPrice.toFixed(4)
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
results.open = [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (args.status === "HISTORY" || args.status === "ALL") {
|
|
67
|
+
const query = { chainId, poolId: args.poolId, limit: args.limit ?? 20 };
|
|
68
|
+
const historyRes = await client.position.getPositionHistory(query, address);
|
|
69
|
+
results.history = (historyRes?.data || []).map((pos) => ({
|
|
70
|
+
...pos,
|
|
71
|
+
directionDesc: getDirectionDesc(pos.direction)
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: results }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
-
import { getMarketPrice } from "../services/marketService.js";
|
|
4
|
-
export const
|
|
5
|
-
name: "
|
|
6
|
-
description: "Get the current
|
|
3
|
+
import { getMarketPrice, getOraclePrice } from "../services/marketService.js";
|
|
4
|
+
export const getPriceTool = {
|
|
5
|
+
name: "get_price",
|
|
6
|
+
description: "[MARKET] Get the current price for a specific pool. Support both market (impact) and oracle prices.",
|
|
7
7
|
schema: {
|
|
8
8
|
poolId: z.string().describe("Pool ID to get price for"),
|
|
9
|
+
priceType: z.enum(["market", "oracle"]).default("market").describe("Type of price to fetch: 'market' (default) or 'oracle'"),
|
|
9
10
|
chainId: z.number().int().positive().optional().describe("Optional chainId override"),
|
|
10
11
|
},
|
|
11
12
|
handler: async (args) => {
|
|
12
13
|
try {
|
|
13
14
|
const { client } = await resolveClient();
|
|
14
15
|
const chainId = args.chainId ?? getChainId();
|
|
15
|
-
const data =
|
|
16
|
+
const data = args.priceType === "oracle"
|
|
17
|
+
? await getOraclePrice(client, args.poolId, chainId)
|
|
18
|
+
: await getMarketPrice(client, args.poolId, chainId);
|
|
16
19
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
17
20
|
}
|
|
18
21
|
catch (error) {
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
3
|
export const getUserTradingFeeRateTool = {
|
|
4
4
|
name: "get_user_trading_fee_rate",
|
|
5
|
-
description: "Get maker/taker fee rates for a given assetClass and riskTier.",
|
|
5
|
+
description: "[TRADE] Get maker/taker fee rates for a given assetClass and riskTier.",
|
|
6
6
|
schema: {
|
|
7
7
|
assetClass: z.coerce.number().int().nonnegative().describe("Asset class ID"),
|
|
8
8
|
riskTier: z.coerce.number().int().nonnegative().describe("Risk tier"),
|
package/dist/tools/index.js
CHANGED
|
@@ -1,36 +1,32 @@
|
|
|
1
1
|
// Tools — 交易
|
|
2
2
|
export { openPositionSimpleTool } from "./openPositionSimple.js";
|
|
3
3
|
export { executeTradeTool } from "./executeTrade.js";
|
|
4
|
-
export {
|
|
5
|
-
export { cancelAllOrdersTool } from "./cancelAllOrders.js";
|
|
4
|
+
export { cancelOrdersTool } from "./cancelOrders.js"; // Unified
|
|
6
5
|
export { closePositionTool } from "./closePosition.js";
|
|
7
|
-
export {
|
|
8
|
-
export { updateOrderTpSlTool } from "./updateOrderTpSl.js";
|
|
6
|
+
export { manageTpSlTool } from "./manageTpSl.js"; // Unified
|
|
9
7
|
export { adjustMarginTool } from "./adjustMargin.js";
|
|
10
8
|
export { closeAllPositionsTool } from "./closeAllPositions.js";
|
|
11
9
|
export { checkApprovalTool } from "./checkApproval.js";
|
|
12
10
|
export { getUserTradingFeeRateTool } from "./getUserTradingFeeRate.js";
|
|
13
11
|
export { getNetworkFeeTool } from "./getNetworkFee.js";
|
|
12
|
+
export { checkAccountReadyTool } from "./checkAccountReady.js";
|
|
14
13
|
// Tools — 市场数据
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export { getAllTickersTool } from "./getAllTickers.js";
|
|
21
|
-
export { getMarketDetailTool, getPoolInfoTool, getLiquidityInfoTool } from "./marketInfo.js";
|
|
22
|
-
export { getPoolListTool } from "./getPoolList.js";
|
|
23
|
-
export { getPoolSymbolAllTool } from "./getPoolSymbolAll.js";
|
|
24
|
-
export { getPoolLevelConfigTool } from "./poolConfig.js";
|
|
14
|
+
export { getPriceTool } from "./getPrice.js"; // Unified
|
|
15
|
+
export { getKlineTool } from "./getKline.js"; // Enhanced
|
|
16
|
+
export { listPoolsTool } from "./listPools.js"; // Unified
|
|
17
|
+
export { findPoolTool } from "./findPool.js"; // Unified
|
|
18
|
+
export { getPoolMetadataTool } from "./getPoolMetadata.js"; // Unified
|
|
25
19
|
export { getBaseDetailTool } from "./getBaseDetail.js";
|
|
20
|
+
export { getAllTickersTool } from "./getAllTickers.js";
|
|
26
21
|
// Tools — 池子 & 流动性
|
|
27
22
|
export { createPerpMarketTool } from "./createPerpMarket.js";
|
|
28
23
|
export { manageLiquidityTool, getLpPriceTool } from "./manageLiquidity.js";
|
|
29
24
|
// Tools — 账户 & 查询
|
|
30
|
-
export {
|
|
31
|
-
export {
|
|
32
|
-
export {
|
|
33
|
-
export {
|
|
25
|
+
export { getPositionsAllTool } from "./getPositionsAll.js"; // Unified
|
|
26
|
+
export { getOrdersTool } from "./getOrders.js"; // Unified
|
|
27
|
+
export { getAccountSnapshotTool } from "./getAccountSnapshot.js"; // Unified
|
|
28
|
+
export { getTradeFlowTool } from "./accountInfo.js"; // Kept for trade flow detail
|
|
34
29
|
export { getMyLpHoldingsTool } from "./getMyLpHoldings.js";
|
|
35
|
-
export { getAccountVipInfoTool } from "./getAccountVipInfo.js";
|
|
36
30
|
export { accountDepositTool, accountWithdrawTool } from "./accountTransfer.js";
|
|
31
|
+
// Tools — 系统
|
|
32
|
+
export { searchToolsTool } from "./searchTools.js";
|