@michaleffffff/mcp-trading-server 2.3.1 → 2.3.2
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/auth/resolveClient.js +10 -7
- package/dist/server.js +31 -17
- package/dist/services/marketService.js +3 -3
- package/dist/services/tradeService.js +18 -11
- package/dist/tools/accountInfo.js +2 -1
- package/dist/tools/accountTransfer.js +2 -1
- package/dist/tools/checkApproval.js +9 -5
- package/dist/tools/closeAllPositions.js +1 -1
- package/dist/tools/closePosition.js +1 -1
- package/dist/tools/executeTrade.js +1 -1
- package/dist/tools/getKline.js +1 -1
- package/dist/tools/getMarketList.js +7 -4
- package/dist/tools/getPositions.js +2 -1
- package/dist/tools/orderQueries.js +2 -1
- package/dist/tools/positionHistory.js +2 -1
- package/dist/tools/setTpSl.js +1 -1
- package/dist/utils/address.js +17 -0
- package/package.json +2 -2
- package/dist/tools/getLpBalances.js +0 -63
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import { MyxClient } from "@myx-trade/sdk";
|
|
2
2
|
import { JsonRpcProvider, Wallet } from "ethers";
|
|
3
|
+
import { normalizeAddress } from "../utils/address.js";
|
|
3
4
|
let cached = null;
|
|
4
5
|
export async function resolveClient() {
|
|
5
6
|
if (cached)
|
|
6
7
|
return cached;
|
|
7
8
|
const rpcUrl = process.env.RPC_URL || "https://rpc.sepolia.linea.build";
|
|
8
9
|
const privateKey = process.env.PRIVATE_KEY;
|
|
9
|
-
const
|
|
10
|
+
const brokerAddressRaw = process.env.BROKER_ADDRESS || "0xAc6C93eaBDc3DBE4e1B0176914dc2a16f8Fd1800";
|
|
10
11
|
const chainId = Number(process.env.CHAIN_ID) || 59141;
|
|
11
|
-
const
|
|
12
|
+
const quoteTokenRaw = process.env.QUOTE_TOKEN_ADDRESS || "0xD984fd34f91F92DA0586e1bE82E262fF27DC431b";
|
|
12
13
|
const quoteDecimals = Number(process.env.QUOTE_TOKEN_DECIMALS) || 6;
|
|
13
14
|
if (!rpcUrl)
|
|
14
15
|
throw new Error("RPC_URL env var is required.");
|
|
15
16
|
if (!privateKey)
|
|
16
17
|
throw new Error("PRIVATE_KEY env var is required.");
|
|
17
|
-
if (!
|
|
18
|
+
if (!brokerAddressRaw)
|
|
18
19
|
throw new Error("BROKER_ADDRESS env var is required.");
|
|
19
|
-
if (!
|
|
20
|
+
if (!quoteTokenRaw)
|
|
20
21
|
throw new Error("QUOTE_TOKEN_ADDRESS env var is required.");
|
|
22
|
+
const brokerAddress = normalizeAddress(brokerAddressRaw, "BROKER_ADDRESS");
|
|
23
|
+
const quoteToken = normalizeAddress(quoteTokenRaw, "QUOTE_TOKEN_ADDRESS");
|
|
21
24
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
22
25
|
const signer = new Wallet(privateKey, provider);
|
|
23
26
|
// Inject the EIP-1193 mock so SDK can sign transactions seamlessly
|
|
@@ -41,10 +44,10 @@ export function getChainId() {
|
|
|
41
44
|
export function getQuoteToken() {
|
|
42
45
|
if (cached)
|
|
43
46
|
return cached.quoteToken;
|
|
44
|
-
const
|
|
45
|
-
if (!
|
|
47
|
+
const tokenRaw = process.env.QUOTE_TOKEN_ADDRESS || "0xD984fd34f91F92DA0586e1bE82E262fF27DC431b";
|
|
48
|
+
if (!tokenRaw)
|
|
46
49
|
throw new Error("QUOTE_TOKEN_ADDRESS env var is required.");
|
|
47
|
-
return
|
|
50
|
+
return normalizeAddress(tokenRaw, "QUOTE_TOKEN_ADDRESS");
|
|
48
51
|
}
|
|
49
52
|
export function getQuoteDecimals() {
|
|
50
53
|
if (cached)
|
package/dist/server.js
CHANGED
|
@@ -35,27 +35,41 @@ const allResources = Object.values(baseResources);
|
|
|
35
35
|
const allPrompts = Object.values(basePrompts);
|
|
36
36
|
// 将 Zod schema 转换为 JSON Schema (tool listing 用)
|
|
37
37
|
function zodSchemaToJsonSchema(zodSchema) {
|
|
38
|
+
const toPropSchema = (value) => {
|
|
39
|
+
const def = value?._def;
|
|
40
|
+
const typeName = def?.typeName;
|
|
41
|
+
if (typeName === "ZodOptional" || typeName === "ZodNullable") {
|
|
42
|
+
return toPropSchema(def?.innerType);
|
|
43
|
+
}
|
|
44
|
+
if (typeName === "ZodString")
|
|
45
|
+
return { type: "string" };
|
|
46
|
+
if (typeName === "ZodNumber")
|
|
47
|
+
return { type: "number" };
|
|
48
|
+
if (typeName === "ZodBoolean")
|
|
49
|
+
return { type: "boolean" };
|
|
50
|
+
if (typeName === "ZodArray") {
|
|
51
|
+
return { type: "array", items: toPropSchema(def?.type) };
|
|
52
|
+
}
|
|
53
|
+
if (typeName === "ZodLiteral") {
|
|
54
|
+
return { const: def?.value };
|
|
55
|
+
}
|
|
56
|
+
if (typeName === "ZodUnion") {
|
|
57
|
+
const options = def?.options || [];
|
|
58
|
+
return { anyOf: options.map((opt) => toPropSchema(opt)) };
|
|
59
|
+
}
|
|
60
|
+
if (typeName === "ZodObject") {
|
|
61
|
+
return { type: "object" };
|
|
62
|
+
}
|
|
63
|
+
return { type: "string" };
|
|
64
|
+
};
|
|
38
65
|
const properties = {};
|
|
39
66
|
const required = [];
|
|
40
67
|
for (const [key, value] of Object.entries(zodSchema)) {
|
|
41
68
|
const desc = value?._def?.description || "";
|
|
42
69
|
const isOptional = value?.isOptional?.() || value?._def?.typeName === "ZodOptional";
|
|
43
|
-
|
|
44
|
-
const innerDef = isOptional ? value?._def?.innerType?._def : value?._def;
|
|
45
|
-
const typeName = innerDef?.typeName;
|
|
46
|
-
let jsonType = "string";
|
|
47
|
-
if (typeName === "ZodNumber")
|
|
48
|
-
jsonType = "number";
|
|
49
|
-
else if (typeName === "ZodBoolean")
|
|
50
|
-
jsonType = "boolean";
|
|
51
|
-
else if (typeName === "ZodArray")
|
|
52
|
-
jsonType = "array";
|
|
53
|
-
const prop = { type: jsonType };
|
|
70
|
+
const prop = toPropSchema(value);
|
|
54
71
|
if (desc)
|
|
55
72
|
prop.description = desc;
|
|
56
|
-
if (jsonType === "array") {
|
|
57
|
-
prop.items = { type: "string" };
|
|
58
|
-
}
|
|
59
73
|
properties[key] = prop;
|
|
60
74
|
if (!isOptional)
|
|
61
75
|
required.push(key);
|
|
@@ -89,9 +103,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
89
103
|
};
|
|
90
104
|
}
|
|
91
105
|
try {
|
|
92
|
-
let validatedArgs = args;
|
|
106
|
+
let validatedArgs = args ?? {};
|
|
93
107
|
if (tool.schema) {
|
|
94
|
-
validatedArgs = z.object(tool.schema).parse(args);
|
|
108
|
+
validatedArgs = z.object(tool.schema).parse(args ?? {});
|
|
95
109
|
}
|
|
96
110
|
logger.toolExecution(name, validatedArgs);
|
|
97
111
|
const result = await tool.handler(validatedArgs);
|
|
@@ -115,7 +129,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
115
129
|
resources: allResources.map((r) => ({
|
|
116
130
|
uri: r.uri,
|
|
117
131
|
name: r.name,
|
|
118
|
-
|
|
132
|
+
mimeType: r.mimetype,
|
|
119
133
|
description: r.description
|
|
120
134
|
}))
|
|
121
135
|
};
|
|
@@ -12,7 +12,7 @@ export async function getOraclePrice(client, poolId) {
|
|
|
12
12
|
const chainId = getChainId();
|
|
13
13
|
return client.utils.getOraclePrice(poolId, chainId);
|
|
14
14
|
}
|
|
15
|
-
export async function searchMarket(client, keyword, limit =
|
|
15
|
+
export async function searchMarket(client, keyword, limit = 1000) {
|
|
16
16
|
const chainId = getChainId();
|
|
17
17
|
const searchRes = await client.markets.searchMarket({ chainId, searchKey: keyword, limit });
|
|
18
18
|
const raw = searchRes;
|
|
@@ -29,8 +29,8 @@ export async function searchMarket(client, keyword, limit = 100) {
|
|
|
29
29
|
else if (raw && Array.isArray(raw)) {
|
|
30
30
|
dataList = raw;
|
|
31
31
|
}
|
|
32
|
-
// Filter
|
|
33
|
-
const activeMarkets = dataList.filter(m => m.state
|
|
32
|
+
// Filter tradable markets (state=2 => Active)
|
|
33
|
+
const activeMarkets = dataList.filter(m => Number(m.state) === 2);
|
|
34
34
|
// Get tickers for these pools to get price and change24h
|
|
35
35
|
let tickers = [];
|
|
36
36
|
if (activeMarkets.length > 0) {
|
|
@@ -2,6 +2,7 @@ import { Direction, OrderType, TriggerType } from "@myx-trade/sdk";
|
|
|
2
2
|
import { parseUnits } from "ethers";
|
|
3
3
|
import { getChainId, getQuoteToken, getQuoteDecimals } from "../auth/resolveClient.js";
|
|
4
4
|
import { ensureUnits, parseHumanUnits } from "../utils/units.js";
|
|
5
|
+
import { normalizeAddress } from "../utils/address.js";
|
|
5
6
|
/**
|
|
6
7
|
* 将人类可读数值转为指定精度的整数字串
|
|
7
8
|
* e.g. toUnits("100", 6) => "100000000"
|
|
@@ -9,6 +10,12 @@ import { ensureUnits, parseHumanUnits } from "../utils/units.js";
|
|
|
9
10
|
export function toUnits(human, decimals) {
|
|
10
11
|
return parseUnits(human, decimals).toString();
|
|
11
12
|
}
|
|
13
|
+
function resolveDirection(direction) {
|
|
14
|
+
if (direction !== 0 && direction !== 1) {
|
|
15
|
+
throw new Error("direction must be 0 (LONG) or 1 (SHORT).");
|
|
16
|
+
}
|
|
17
|
+
return direction === 0 ? Direction.LONG : Direction.SHORT;
|
|
18
|
+
}
|
|
12
19
|
/**
|
|
13
20
|
* 开仓 / 加仓
|
|
14
21
|
*/
|
|
@@ -18,7 +25,7 @@ export async function openPosition(client, address, args) {
|
|
|
18
25
|
throw new Error(`Security Exception: collateralAmount (${args.collateralAmount}) exceeds MAX_TRADE_AMOUNT (${maxTradeAmount}). Update .env if you need to trade larger sizes.`);
|
|
19
26
|
}
|
|
20
27
|
const chainId = getChainId();
|
|
21
|
-
const quoteToken = args.quoteToken || getQuoteToken();
|
|
28
|
+
const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
|
|
22
29
|
const quoteDecimals = args.quoteDecimals ?? getQuoteDecimals();
|
|
23
30
|
const rawLeverage = args.leverage ?? 10;
|
|
24
31
|
if (!Number.isFinite(rawLeverage) || rawLeverage <= 0) {
|
|
@@ -53,7 +60,7 @@ export async function openPosition(client, address, args) {
|
|
|
53
60
|
const exponent = BigInt(18 + 30 - quoteDecimals);
|
|
54
61
|
size = ((collateralBig * leverageBig * (10n ** exponent)) / priceBig).toString();
|
|
55
62
|
}
|
|
56
|
-
const dir = args.direction
|
|
63
|
+
const dir = resolveDirection(args.direction);
|
|
57
64
|
// 根据方向决定 triggerType: 做多分级(如果现价>限价则是低买,需等跌,用 LTE;反之亦然,这里简化为通用 LTE(多)/GTE(空))
|
|
58
65
|
const triggerType = args.price
|
|
59
66
|
? (args.direction === 0 ? TriggerType.LTE : TriggerType.GTE)
|
|
@@ -72,7 +79,7 @@ export async function openPosition(client, address, args) {
|
|
|
72
79
|
price: orderPrice30,
|
|
73
80
|
timeInForce: 0,
|
|
74
81
|
postOnly: false,
|
|
75
|
-
slippagePct: args.slippagePct
|
|
82
|
+
slippagePct: args.slippagePct ?? "100", // BPS
|
|
76
83
|
executionFeeToken: quoteToken,
|
|
77
84
|
leverage,
|
|
78
85
|
};
|
|
@@ -100,12 +107,12 @@ export async function openPosition(client, address, args) {
|
|
|
100
107
|
*/
|
|
101
108
|
export async function closePosition(client, address, args) {
|
|
102
109
|
const chainId = getChainId();
|
|
103
|
-
const quoteToken = args.quoteToken || getQuoteToken();
|
|
110
|
+
const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
|
|
104
111
|
if (args.direction === undefined || args.direction === null) {
|
|
105
112
|
throw new Error("direction is required (0=LONG, 1=SHORT), must match position direction.");
|
|
106
113
|
}
|
|
107
114
|
const price30 = parseHumanUnits(args.price, 30, "price");
|
|
108
|
-
const dir = args.direction
|
|
115
|
+
const dir = resolveDirection(args.direction);
|
|
109
116
|
return client.order.createDecreaseOrder({
|
|
110
117
|
chainId,
|
|
111
118
|
address,
|
|
@@ -118,7 +125,7 @@ export async function closePosition(client, address, args) {
|
|
|
118
125
|
size: args.size,
|
|
119
126
|
price: price30,
|
|
120
127
|
postOnly: false,
|
|
121
|
-
slippagePct: args.slippagePct
|
|
128
|
+
slippagePct: args.slippagePct ?? "100",
|
|
122
129
|
executionFeeToken: quoteToken,
|
|
123
130
|
leverage: args.leverage,
|
|
124
131
|
});
|
|
@@ -128,11 +135,11 @@ export async function closePosition(client, address, args) {
|
|
|
128
135
|
*/
|
|
129
136
|
export async function setPositionTpSl(client, address, args) {
|
|
130
137
|
const chainId = getChainId();
|
|
131
|
-
const quoteToken = args.quoteToken || getQuoteToken();
|
|
138
|
+
const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
|
|
132
139
|
if (!args.tpPrice && !args.slPrice) {
|
|
133
140
|
throw new Error("At least one of tpPrice or slPrice must be provided.");
|
|
134
141
|
}
|
|
135
|
-
const dir = args.direction
|
|
142
|
+
const dir = resolveDirection(args.direction);
|
|
136
143
|
const params = {
|
|
137
144
|
chainId,
|
|
138
145
|
address,
|
|
@@ -160,7 +167,7 @@ export async function setPositionTpSl(client, address, args) {
|
|
|
160
167
|
*/
|
|
161
168
|
export async function adjustMargin(client, address, args) {
|
|
162
169
|
const chainId = getChainId();
|
|
163
|
-
const quoteToken = args.quoteToken || getQuoteToken();
|
|
170
|
+
const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
|
|
164
171
|
const quoteDecimals = args.quoteDecimals ?? getQuoteDecimals();
|
|
165
172
|
return client.position.adjustCollateral({
|
|
166
173
|
poolId: args.poolId,
|
|
@@ -197,7 +204,7 @@ export async function closeAllPositions(client, address) {
|
|
|
197
204
|
else {
|
|
198
205
|
slippagePrice30 = (BigInt(currentPrice30) * 110n) / 100n;
|
|
199
206
|
}
|
|
200
|
-
const sizeWei =
|
|
207
|
+
const sizeWei = ensureUnits(pos.size, 18, "size");
|
|
201
208
|
const res = await client.order.createDecreaseOrder({
|
|
202
209
|
chainId,
|
|
203
210
|
address,
|
|
@@ -223,7 +230,7 @@ export async function closeAllPositions(client, address) {
|
|
|
223
230
|
*/
|
|
224
231
|
export async function updateOrderTpSl(client, address, args) {
|
|
225
232
|
const chainId = getChainId();
|
|
226
|
-
const quoteToken = args.quoteToken || getQuoteToken();
|
|
233
|
+
const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
|
|
227
234
|
const params = {
|
|
228
235
|
orderId: args.orderId,
|
|
229
236
|
tpSize: args.tpSize || "0",
|
|
@@ -47,7 +47,8 @@ export const getTradeFlowTool = {
|
|
|
47
47
|
try {
|
|
48
48
|
const { client, address } = await resolveClient();
|
|
49
49
|
const chainId = getChainId();
|
|
50
|
-
const
|
|
50
|
+
const query = { chainId, page: args.page ?? 1, limit: args.limit ?? 20 };
|
|
51
|
+
const result = await client.account.getTradeFlow(query, address);
|
|
51
52
|
const enhancedData = (result?.data || []).map((flow) => ({
|
|
52
53
|
...flow,
|
|
53
54
|
typeDesc: getTradeFlowTypeDesc(flow.type)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
|
|
3
|
+
import { normalizeAddress } from "../utils/address.js";
|
|
3
4
|
export const accountDepositTool = {
|
|
4
5
|
name: "account_deposit",
|
|
5
6
|
description: "Deposit funds from wallet into the MYX trading account.",
|
|
@@ -11,7 +12,7 @@ export const accountDepositTool = {
|
|
|
11
12
|
try {
|
|
12
13
|
const { client } = await resolveClient();
|
|
13
14
|
const chainId = getChainId();
|
|
14
|
-
const tokenAddress = args.tokenAddress || getQuoteToken();
|
|
15
|
+
const tokenAddress = normalizeAddress(args.tokenAddress || getQuoteToken(), "tokenAddress");
|
|
15
16
|
const result = await client.account.deposit({
|
|
16
17
|
amount: args.amount,
|
|
17
18
|
tokenAddress,
|
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
|
|
3
|
+
import { normalizeAddress } from "../utils/address.js";
|
|
4
|
+
const MAX_UINT256 = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
|
|
3
5
|
export const checkApprovalTool = {
|
|
4
6
|
name: "check_approval",
|
|
5
|
-
description: "Check if token spending approval is needed.
|
|
7
|
+
description: "Check if token spending approval is needed. Supports auto-approve exact amount (default) or optional unlimited approval.",
|
|
6
8
|
schema: {
|
|
7
9
|
amount: z.string().describe("Amount to check approval for (in token decimals)"),
|
|
8
10
|
quoteToken: z.string().optional().describe("Token address to check. Uses default if omitted."),
|
|
9
|
-
autoApprove: z.boolean().optional().describe("If true, automatically approve
|
|
11
|
+
autoApprove: z.boolean().optional().describe("If true, automatically approve when needed."),
|
|
12
|
+
approveMax: z.boolean().optional().describe("If true with autoApprove, approve unlimited MaxUint256 (default false: approve exact amount only)."),
|
|
10
13
|
},
|
|
11
14
|
handler: async (args) => {
|
|
12
15
|
try {
|
|
13
16
|
const { client, address } = await resolveClient();
|
|
14
17
|
const chainId = getChainId();
|
|
15
|
-
const quoteToken = args.quoteToken || getQuoteToken();
|
|
18
|
+
const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
|
|
16
19
|
const needApproval = await client.utils.needsApproval(address, chainId, quoteToken, args.amount);
|
|
17
20
|
if (needApproval && args.autoApprove) {
|
|
21
|
+
const approveAmount = args.approveMax ? MAX_UINT256 : args.amount;
|
|
18
22
|
await client.utils.approveAuthorization({
|
|
19
23
|
chainId,
|
|
20
24
|
quoteAddress: quoteToken,
|
|
21
|
-
amount:
|
|
25
|
+
amount: approveAmount,
|
|
22
26
|
});
|
|
23
|
-
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: { needApproval: true, approved: true, quoteToken } }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
27
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: { needApproval: true, approved: true, quoteToken, approvedAmount: approveAmount, approveMax: !!args.approveMax } }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
24
28
|
}
|
|
25
29
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: { needApproval, approved: false, quoteToken } }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
26
30
|
}
|
|
@@ -24,7 +24,7 @@ export const closeAllPositionsTool = {
|
|
|
24
24
|
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) }] };
|
|
25
25
|
}
|
|
26
26
|
// 2) 为每个仓位构建平仓参数
|
|
27
|
-
const slippagePct = args.slippagePct
|
|
27
|
+
const slippagePct = args.slippagePct ?? "200";
|
|
28
28
|
// 3) We need actual oracle prices to avoid Revert 0x613970e0 (InvalidParameter)
|
|
29
29
|
const oraclePriceReq = await getOraclePrice(client, args.poolId).catch(() => null);
|
|
30
30
|
let fallbackPrice = "0";
|
|
@@ -7,7 +7,7 @@ export const closePositionTool = {
|
|
|
7
7
|
schema: {
|
|
8
8
|
poolId: z.string().describe("Pool ID"),
|
|
9
9
|
positionId: z.string().describe("Position ID to close"),
|
|
10
|
-
direction: z.
|
|
10
|
+
direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT (must match position direction)"),
|
|
11
11
|
leverage: z.number().describe("Position leverage"),
|
|
12
12
|
size: z.string().describe("Size to close (18 decimals)"),
|
|
13
13
|
price: z.string().describe("Price in human-readable units"),
|
|
@@ -6,7 +6,7 @@ export const executeTradeTool = {
|
|
|
6
6
|
description: "Execute a new trade or add to an existing position.",
|
|
7
7
|
schema: {
|
|
8
8
|
poolId: z.string().describe("Pool ID to trade in"),
|
|
9
|
-
direction: z.
|
|
9
|
+
direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT"),
|
|
10
10
|
collateralAmount: z.string().describe("Collateral in human-readable units (e.g. '100' for 100 USDC)"),
|
|
11
11
|
leverage: z.number().optional().describe("Leverage multiplier (e.g. 10, default 10)"),
|
|
12
12
|
price: z.string().optional().describe("Limit price in human-readable units (e.g. '3000'). Required for LIMIT orders, omitted for MARKET orders."),
|
package/dist/tools/getKline.js
CHANGED
|
@@ -16,7 +16,7 @@ export const getKlineTool = {
|
|
|
16
16
|
poolId: args.poolId,
|
|
17
17
|
chainId,
|
|
18
18
|
interval: args.interval,
|
|
19
|
-
limit: args.limit
|
|
19
|
+
limit: args.limit ?? 100,
|
|
20
20
|
endTime: Date.now(),
|
|
21
21
|
});
|
|
22
22
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { resolveClient } from "../auth/resolveClient.js";
|
|
2
2
|
import { searchMarket } from "../services/marketService.js";
|
|
3
|
+
import { z } from "zod";
|
|
3
4
|
export const getMarketListTool = {
|
|
4
5
|
name: "get_market_list",
|
|
5
|
-
description: "Get
|
|
6
|
-
schema: {
|
|
7
|
-
|
|
6
|
+
description: "Get tradable markets/pools (state=2 Active). Supports configurable result limit; backend may still enforce its own cap.",
|
|
7
|
+
schema: {
|
|
8
|
+
limit: z.number().optional().describe("Max results to request (default 1000)."),
|
|
9
|
+
},
|
|
10
|
+
handler: async (args) => {
|
|
8
11
|
try {
|
|
9
12
|
const { client } = await resolveClient();
|
|
10
|
-
const activeMarkets = await searchMarket(client, "");
|
|
13
|
+
const activeMarkets = await searchMarket(client, "", args.limit ?? 1000);
|
|
11
14
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: activeMarkets }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
12
15
|
}
|
|
13
16
|
catch (error) {
|
|
@@ -9,7 +9,8 @@ export const getPositionsTool = {
|
|
|
9
9
|
try {
|
|
10
10
|
const { client, address } = await resolveClient();
|
|
11
11
|
const data = await getPositions(client, address);
|
|
12
|
-
const
|
|
12
|
+
const positions = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
|
|
13
|
+
const enhancedData = positions.map((pos) => ({
|
|
13
14
|
...pos,
|
|
14
15
|
directionDesc: getDirectionDesc(pos.direction)
|
|
15
16
|
}));
|
|
@@ -34,7 +34,8 @@ export const getOrderHistoryTool = {
|
|
|
34
34
|
try {
|
|
35
35
|
const { client, address } = await resolveClient();
|
|
36
36
|
const chainId = getChainId();
|
|
37
|
-
const
|
|
37
|
+
const query = { chainId, poolId: args.poolId, page: args.page ?? 1, limit: args.limit ?? 20 };
|
|
38
|
+
const result = await client.order.getOrderHistory(query, address);
|
|
38
39
|
const enhancedData = (result?.data || []).map((order) => ({
|
|
39
40
|
...order,
|
|
40
41
|
orderTypeDesc: getOrderTypeDesc(order.orderType),
|
|
@@ -13,7 +13,8 @@ export const getPositionHistoryTool = {
|
|
|
13
13
|
try {
|
|
14
14
|
const { client, address } = await resolveClient();
|
|
15
15
|
const chainId = getChainId();
|
|
16
|
-
const
|
|
16
|
+
const query = { chainId, poolId: args.poolId, page: args.page ?? 1, limit: args.limit ?? 20 };
|
|
17
|
+
const result = await client.position.getPositionHistory(query, address);
|
|
17
18
|
const enhancedData = (result?.data || []).map((pos) => ({
|
|
18
19
|
...pos,
|
|
19
20
|
directionDesc: getDirectionDesc(pos.direction),
|
package/dist/tools/setTpSl.js
CHANGED
|
@@ -7,7 +7,7 @@ export const setTpSlTool = {
|
|
|
7
7
|
schema: {
|
|
8
8
|
poolId: z.string().describe("Pool ID"),
|
|
9
9
|
positionId: z.string().describe("Position ID"),
|
|
10
|
-
direction: z.
|
|
10
|
+
direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT (position direction)"),
|
|
11
11
|
leverage: z.number().describe("Position leverage"),
|
|
12
12
|
tpPrice: z.string().optional().describe("Take-profit trigger price (human-readable)"),
|
|
13
13
|
tpSize: z.string().optional().describe("TP size (18 decimals). Omit or '0' for full position."),
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getAddress } from "ethers";
|
|
2
|
+
export function normalizeAddress(value, label = "address") {
|
|
3
|
+
const raw = String(value || "").trim();
|
|
4
|
+
if (!raw)
|
|
5
|
+
throw new Error(`${label} is required.`);
|
|
6
|
+
try {
|
|
7
|
+
return getAddress(raw);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
try {
|
|
11
|
+
return getAddress(raw.toLowerCase());
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
throw new Error(`${label} is not a valid EVM address: ${raw}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michaleffffff/mcp-trading-server",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"myx-mcp": "dist/server.js"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "tsc",
|
|
12
|
+
"build": "rm -rf dist && tsc",
|
|
13
13
|
"start": "node dist/server.js",
|
|
14
14
|
"dev": "tsx src/server.ts",
|
|
15
15
|
"test": "tsx tests/test_sepolia.ts"
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
BigInt.prototype.toJSON = function () { return this.toString(); };
|
|
4
|
-
require("dotenv/config");
|
|
5
|
-
const resolveClient_1 = require("../auth/resolveClient");
|
|
6
|
-
const ethers_1 = require("ethers");
|
|
7
|
-
async function main() {
|
|
8
|
-
console.log("═══════════════════════════════════════");
|
|
9
|
-
console.log(" 查询当前用户的 LP 持仓");
|
|
10
|
-
console.log("═══════════════════════════════════════\n");
|
|
11
|
-
const { client, address } = await (0, resolveClient_1.resolveClient)();
|
|
12
|
-
const chainId = 59141;
|
|
13
|
-
console.log(` 用户地址: ${address}`);
|
|
14
|
-
console.log(` 开始获取所有市场池子...`);
|
|
15
|
-
try {
|
|
16
|
-
const listRes = await client.api.getMarketList();
|
|
17
|
-
if (!listRes.data || !Array.length) {
|
|
18
|
-
console.log("未找到任何池子");
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const pools = listRes.data;
|
|
22
|
-
console.log(` 共查到 ${pools.length} 个池子,逐个检查您的 LP 余额...\n`);
|
|
23
|
-
let foundLP = false;
|
|
24
|
-
for (const p of pools) {
|
|
25
|
-
try {
|
|
26
|
-
const info = await client.account.getAccountInfo(chainId, address, p.poolId);
|
|
27
|
-
// info.data details user LP data
|
|
28
|
-
if (info && info.data) {
|
|
29
|
-
const lps = info.data.lpBalances || info.data.poolShares || info.data;
|
|
30
|
-
let hasBalance = false;
|
|
31
|
-
let balanceFormatted = "0";
|
|
32
|
-
// Depending on API response structure, there may be baseAmount / quoteAmount
|
|
33
|
-
// But LP token itself balance should be checked
|
|
34
|
-
// Let's print the entire raw structure if quoteAmount > 0
|
|
35
|
-
if (info.data.quoteAmount && BigInt(info.data.quoteAmount) > 0n) {
|
|
36
|
-
hasBalance = true;
|
|
37
|
-
balanceFormatted = ethers_1.ethers.formatUnits(info.data.quoteAmount, 6) + " USDC";
|
|
38
|
-
}
|
|
39
|
-
if (info.data.baseAmount && BigInt(info.data.baseAmount) > 0n) {
|
|
40
|
-
hasBalance = true;
|
|
41
|
-
balanceFormatted += " / " + ethers_1.ethers.formatUnits(info.data.baseAmount, 18) + " BASE";
|
|
42
|
-
}
|
|
43
|
-
if (hasBalance || Object.keys(info.data).some(k => k.toLowerCase().includes("balance") && info.data[k] !== "0")) {
|
|
44
|
-
foundLP = true;
|
|
45
|
-
console.log(`✅ 您在 [${p.symbol || p.name || p.poolId}] (${p.poolId}) 有流动性份额!`);
|
|
46
|
-
console.log(" 当前池子数据:", JSON.stringify(info.data, null, 2));
|
|
47
|
-
console.log("-----------------------------------------");
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
catch (e) {
|
|
52
|
-
// Skip pools with errors
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (!foundLP) {
|
|
56
|
-
console.log(" ⚠️ 您当前没有任何池子的 LP 持仓。");
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
console.error("❌ 查询失败:", err.message);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
main().catch(err => console.error("Fatal:", err));
|