@michaleffffff/mcp-trading-server 3.0.7 → 3.0.15
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 +101 -0
- package/README.md +93 -2
- package/TOOL_EXAMPLES.md +301 -51
- package/dist/prompts/tradingGuide.js +4 -2
- package/dist/server.js +95 -4
- package/dist/services/poolService.js +430 -6
- package/dist/services/tradeService.js +62 -2
- package/dist/tools/closePosition.js +1 -1
- package/dist/tools/createPerpMarket.js +132 -5
- package/dist/tools/executeTrade.js +6 -1
- package/dist/tools/getBaseDetail.js +129 -3
- package/dist/tools/getOrders.js +30 -2
- package/dist/tools/manageTpSl.js +162 -25
- package/dist/tools/openPositionSimple.js +5 -0
- package/dist/tools/searchTools.js +237 -12
- package/dist/utils/injectProvider.js +1 -0
- package/dist/utils/mappings.js +23 -7
- package/package.json +3 -2
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Direction, OrderType, TriggerType } from "@myx-trade/sdk";
|
|
2
|
+
import { formatUnits } from "ethers";
|
|
2
3
|
import { getChainId, getQuoteToken, getQuoteDecimals } from "../auth/resolveClient.js";
|
|
3
4
|
import { ensureUnits } from "../utils/units.js";
|
|
4
5
|
import { normalizeAddress } from "../utils/address.js";
|
|
5
6
|
import { normalizeSlippagePct4dp } from "../utils/slippage.js";
|
|
6
7
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
7
8
|
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
9
|
+
import { mapTimeInForce } from "../utils/mappings.js";
|
|
8
10
|
function resolveDirection(direction) {
|
|
9
11
|
if (typeof direction === "string") {
|
|
10
12
|
const text = direction.trim().toUpperCase();
|
|
@@ -104,6 +106,54 @@ function toBigIntOrZero(value) {
|
|
|
104
106
|
return 0n;
|
|
105
107
|
}
|
|
106
108
|
}
|
|
109
|
+
const ORDER_VALUE_SCALE = 1000000n;
|
|
110
|
+
const DECIMAL_INPUT_RE = /^\d+(\.\d+)?$/;
|
|
111
|
+
function parseScaledDecimal(value, scale, label) {
|
|
112
|
+
const text = String(value ?? "").trim();
|
|
113
|
+
if (!text)
|
|
114
|
+
throw new Error(`${label} is required.`);
|
|
115
|
+
if (!DECIMAL_INPUT_RE.test(text)) {
|
|
116
|
+
throw new Error(`${label} must be numeric.`);
|
|
117
|
+
}
|
|
118
|
+
const [integerPart, fractionPart = ""] = text.split(".");
|
|
119
|
+
const normalizedFraction = fractionPart.slice(0, scale).padEnd(scale, "0");
|
|
120
|
+
return BigInt(`${integerPart}${normalizedFraction}`);
|
|
121
|
+
}
|
|
122
|
+
function absBigInt(value) {
|
|
123
|
+
return value < 0n ? -value : value;
|
|
124
|
+
}
|
|
125
|
+
function computeQuoteNotionalRaw(sizeRaw, priceRaw30, baseDecimals, quoteDecimals) {
|
|
126
|
+
const numerator = sizeRaw * priceRaw30 * (10n ** BigInt(quoteDecimals));
|
|
127
|
+
const denominator = 10n ** BigInt(baseDecimals + 30);
|
|
128
|
+
return numerator / denominator;
|
|
129
|
+
}
|
|
130
|
+
function computeRecommendedSizeRaw(targetQuoteRaw, priceRaw30, baseDecimals, quoteDecimals) {
|
|
131
|
+
const numerator = targetQuoteRaw * (10n ** BigInt(baseDecimals + 30));
|
|
132
|
+
const denominator = priceRaw30 * (10n ** BigInt(quoteDecimals));
|
|
133
|
+
return numerator / denominator;
|
|
134
|
+
}
|
|
135
|
+
function validateIncreaseOrderEconomics(args) {
|
|
136
|
+
const collateralRawBig = BigInt(args.collateralRaw);
|
|
137
|
+
const sizeRawBig = BigInt(args.sizeRaw);
|
|
138
|
+
const priceRawBig = BigInt(args.priceRaw30);
|
|
139
|
+
if (collateralRawBig <= 0n || sizeRawBig <= 0n || priceRawBig <= 0n)
|
|
140
|
+
return;
|
|
141
|
+
const leverageScaled = parseScaledDecimal(args.leverage, 6, "leverage");
|
|
142
|
+
const targetQuoteRaw = (collateralRawBig * leverageScaled) / ORDER_VALUE_SCALE;
|
|
143
|
+
const actualQuoteRaw = computeQuoteNotionalRaw(sizeRawBig, priceRawBig, args.baseDecimals, args.quoteDecimals);
|
|
144
|
+
const deltaRaw = absBigInt(actualQuoteRaw - targetQuoteRaw);
|
|
145
|
+
const minToleranceRaw = args.quoteDecimals >= 2 ? 10n ** BigInt(args.quoteDecimals - 2) : 1n;
|
|
146
|
+
const pctToleranceRaw = targetQuoteRaw / 100n;
|
|
147
|
+
const toleranceRaw = pctToleranceRaw > minToleranceRaw ? pctToleranceRaw : minToleranceRaw;
|
|
148
|
+
if (deltaRaw <= toleranceRaw)
|
|
149
|
+
return;
|
|
150
|
+
const recommendedSizeRaw = computeRecommendedSizeRaw(targetQuoteRaw, priceRawBig, args.baseDecimals, args.quoteDecimals);
|
|
151
|
+
const targetHuman = formatUnits(targetQuoteRaw, args.quoteDecimals);
|
|
152
|
+
const actualHuman = formatUnits(actualQuoteRaw, args.quoteDecimals);
|
|
153
|
+
const recommendedSizeHuman = formatUnits(recommendedSizeRaw, args.baseDecimals);
|
|
154
|
+
const priceHuman = formatUnits(priceRawBig, 30);
|
|
155
|
+
throw new Error(`Invalid size semantics: size is BASE quantity, not USD notional. collateralAmount*leverage implies ≈${targetHuman} quote, but size*price implies ≈${actualHuman} quote. At price ${priceHuman}, recommended size is ≈${recommendedSizeHuman}.`);
|
|
156
|
+
}
|
|
107
157
|
async function resolveDecimalsForUpdateOrder(client, chainId, marketId, poolIdHint) {
|
|
108
158
|
let baseDecimals = 18;
|
|
109
159
|
let quoteDecimals = getQuoteDecimals();
|
|
@@ -199,6 +249,15 @@ export async function openPosition(client, address, args) {
|
|
|
199
249
|
const sizeRaw = ensureUnits(args.size, baseDecimals, "size", { allowImplicitRaw: false });
|
|
200
250
|
const priceRaw = ensureUnits(args.price, 30, "price", { allowImplicitRaw: false });
|
|
201
251
|
const tradingFeeRaw = ensureUnits(args.tradingFee, quoteDecimals, "tradingFee", { allowImplicitRaw: false });
|
|
252
|
+
validateIncreaseOrderEconomics({
|
|
253
|
+
collateralRaw,
|
|
254
|
+
sizeRaw,
|
|
255
|
+
priceRaw30: priceRaw,
|
|
256
|
+
leverage: args.leverage,
|
|
257
|
+
baseDecimals,
|
|
258
|
+
quoteDecimals,
|
|
259
|
+
});
|
|
260
|
+
const timeInForce = mapTimeInForce(args.timeInForce);
|
|
202
261
|
// --- Auto-Deposit Logic (Strict) ---
|
|
203
262
|
const allowAutoDeposit = args.autoDeposit !== false;
|
|
204
263
|
console.log(`[tradeService] Checking marginBalance for ${address} in pool ${args.poolId}...`);
|
|
@@ -247,7 +306,7 @@ export async function openPosition(client, address, args) {
|
|
|
247
306
|
collateralAmount: collateralRaw,
|
|
248
307
|
size: sizeRaw,
|
|
249
308
|
price: priceRaw,
|
|
250
|
-
timeInForce
|
|
309
|
+
timeInForce,
|
|
251
310
|
postOnly: args.postOnly,
|
|
252
311
|
slippagePct: normalizeSlippagePct4dp(args.slippagePct),
|
|
253
312
|
executionFeeToken,
|
|
@@ -282,6 +341,7 @@ export async function closePosition(client, address, args) {
|
|
|
282
341
|
}
|
|
283
342
|
const baseDecimals = poolData.baseDecimals || 18;
|
|
284
343
|
const quoteDecimals = poolData.quoteDecimals || 6;
|
|
344
|
+
const timeInForce = mapTimeInForce(args.timeInForce);
|
|
285
345
|
return client.order.createDecreaseOrder({
|
|
286
346
|
chainId,
|
|
287
347
|
address,
|
|
@@ -293,7 +353,7 @@ export async function closePosition(client, address, args) {
|
|
|
293
353
|
collateralAmount: ensureUnits(args.collateralAmount, quoteDecimals, "collateralAmount", { allowImplicitRaw: false }),
|
|
294
354
|
size: ensureUnits(args.size, baseDecimals, "size", { allowImplicitRaw: false }),
|
|
295
355
|
price: ensureUnits(args.price, 30, "price", { allowImplicitRaw: false }),
|
|
296
|
-
timeInForce
|
|
356
|
+
timeInForce,
|
|
297
357
|
postOnly: args.postOnly,
|
|
298
358
|
slippagePct: normalizeSlippagePct4dp(args.slippagePct),
|
|
299
359
|
executionFeeToken,
|
|
@@ -41,7 +41,7 @@ export const closePositionTool = {
|
|
|
41
41
|
collateralAmount: z.union([z.string(), z.number()]).describe("Collateral amount (human/raw). Also supports ALL/FULL/MAX to use live position collateral raw."),
|
|
42
42
|
size: z.union([z.string(), z.number()]).describe("Position size (human/raw). Also supports ALL/FULL/MAX for exact full-close raw size."),
|
|
43
43
|
price: z.union([z.string(), z.number()]).describe("Price (human or 30-dec raw units)"),
|
|
44
|
-
timeInForce: z.
|
|
44
|
+
timeInForce: z.union([z.number(), z.string()]).describe("SDK v1.0.2 supports IOC only. Use 0 or 'IOC'."),
|
|
45
45
|
postOnly: z.coerce.boolean().describe("Post-only flag"),
|
|
46
46
|
slippagePct: z.coerce.string().default("50").describe(SLIPPAGE_PCT_4DP_DESC),
|
|
47
47
|
executionFeeToken: z.string().describe("Execution fee token address"),
|
|
@@ -1,7 +1,115 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { createPool } from "../services/poolService.js";
|
|
3
|
-
import { resolveClient } from "../auth/resolveClient.js";
|
|
2
|
+
import { createPool, getMarketPoolByBaseToken } from "../services/poolService.js";
|
|
3
|
+
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
4
4
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
5
|
+
import { normalizeAddress } from "../utils/address.js";
|
|
6
|
+
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
7
|
+
import { decodeErrorSelector } from "../utils/errors.js";
|
|
8
|
+
const MARKET_ID_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
9
|
+
function compactMessage(message) {
|
|
10
|
+
const flat = String(message ?? "").replace(/\s+/g, " ").trim();
|
|
11
|
+
if (!flat)
|
|
12
|
+
return "Unknown create_perp_market error.";
|
|
13
|
+
if (flat.length <= 420)
|
|
14
|
+
return flat;
|
|
15
|
+
return `${flat.slice(0, 420)}...`;
|
|
16
|
+
}
|
|
17
|
+
function extractSelectorCandidate(input) {
|
|
18
|
+
const queue = [input];
|
|
19
|
+
const visited = new Set();
|
|
20
|
+
const LONG_HEX_RE = /0x[0-9a-fA-F]{8,}/g;
|
|
21
|
+
const EXACT_SELECTOR_RE = /0x[0-9a-fA-F]{8}\b/g;
|
|
22
|
+
const KEYED_SELECTOR_RE = /\b(?:data|selector|error|revert)\s*[:=]\s*["']?(0x[0-9a-fA-F]{8,})/gi;
|
|
23
|
+
let fallback = null;
|
|
24
|
+
while (queue.length > 0) {
|
|
25
|
+
const current = queue.shift();
|
|
26
|
+
if (current === null || current === undefined)
|
|
27
|
+
continue;
|
|
28
|
+
if (visited.has(current))
|
|
29
|
+
continue;
|
|
30
|
+
visited.add(current);
|
|
31
|
+
if (typeof current === "string") {
|
|
32
|
+
const exactMatches = current.match(EXACT_SELECTOR_RE) ?? [];
|
|
33
|
+
for (const candidate of exactMatches) {
|
|
34
|
+
const normalized = candidate.toLowerCase();
|
|
35
|
+
if (decodeErrorSelector(normalized))
|
|
36
|
+
return normalized;
|
|
37
|
+
if (!fallback)
|
|
38
|
+
fallback = normalized;
|
|
39
|
+
}
|
|
40
|
+
for (const match of current.matchAll(KEYED_SELECTOR_RE)) {
|
|
41
|
+
const raw = String(match[1] ?? "").toLowerCase();
|
|
42
|
+
if (raw.length < 10)
|
|
43
|
+
continue;
|
|
44
|
+
const selector = `0x${raw.slice(2, 10)}`;
|
|
45
|
+
if (decodeErrorSelector(selector))
|
|
46
|
+
return selector;
|
|
47
|
+
if (!fallback)
|
|
48
|
+
fallback = selector;
|
|
49
|
+
}
|
|
50
|
+
const matches = current.match(LONG_HEX_RE) ?? [];
|
|
51
|
+
for (const hex of matches) {
|
|
52
|
+
const normalized = hex.toLowerCase();
|
|
53
|
+
if (normalized.length === 42)
|
|
54
|
+
continue; // likely plain address
|
|
55
|
+
if (normalized.length < 10)
|
|
56
|
+
continue;
|
|
57
|
+
const selector = `0x${normalized.slice(2, 10)}`;
|
|
58
|
+
if (decodeErrorSelector(selector)) {
|
|
59
|
+
return selector;
|
|
60
|
+
}
|
|
61
|
+
if (!fallback)
|
|
62
|
+
fallback = selector;
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (typeof current !== "object")
|
|
67
|
+
continue;
|
|
68
|
+
const record = current;
|
|
69
|
+
for (const value of Object.values(record)) {
|
|
70
|
+
queue.push(value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return fallback;
|
|
74
|
+
}
|
|
75
|
+
function buildCreateMarketErrorPayload(args, messageLike) {
|
|
76
|
+
const message = compactMessage(extractErrorMessage(messageLike, "create_perp_market failed."));
|
|
77
|
+
const selectorCandidate = extractSelectorCandidate(messageLike);
|
|
78
|
+
const decodedSelector = selectorCandidate ? decodeErrorSelector(selectorCandidate) : null;
|
|
79
|
+
const lower = message.toLowerCase();
|
|
80
|
+
const code = lower.includes("marketid") && lower.includes("66")
|
|
81
|
+
? "INVALID_PARAM"
|
|
82
|
+
: lower.includes("not a valid evm address")
|
|
83
|
+
? "INVALID_PARAM"
|
|
84
|
+
: lower.includes("abi") && lower.includes("size")
|
|
85
|
+
? "INVALID_PARAM"
|
|
86
|
+
: lower.includes("reverted")
|
|
87
|
+
? "CONTRACT_REVERT"
|
|
88
|
+
: "TOOL_EXECUTION_ERROR";
|
|
89
|
+
const decoratedMessage = decodedSelector
|
|
90
|
+
? `${message} (Decoded Contract Error: ${decodedSelector})`
|
|
91
|
+
: message;
|
|
92
|
+
const hint = code === "INVALID_PARAM"
|
|
93
|
+
? "Use a valid baseToken address and a 66-char marketId config hash (0x + 64 hex)."
|
|
94
|
+
: "Check market allocation / permissions / duplicate pool status, then retry.";
|
|
95
|
+
return {
|
|
96
|
+
status: "error",
|
|
97
|
+
error: {
|
|
98
|
+
tool: "create_perp_market",
|
|
99
|
+
code,
|
|
100
|
+
message: decoratedMessage,
|
|
101
|
+
hint,
|
|
102
|
+
action: "Adjust parameters or prerequisites and retry.",
|
|
103
|
+
details: {
|
|
104
|
+
chainId: getChainId(),
|
|
105
|
+
baseToken: args?.baseToken ?? null,
|
|
106
|
+
marketId: args?.marketId ?? null,
|
|
107
|
+
selector: selectorCandidate,
|
|
108
|
+
decodedSelector,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
5
113
|
export const createPerpMarketTool = {
|
|
6
114
|
name: "create_perp_market",
|
|
7
115
|
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.",
|
|
@@ -13,13 +121,32 @@ export const createPerpMarketTool = {
|
|
|
13
121
|
try {
|
|
14
122
|
if (!args.baseToken || !args.marketId)
|
|
15
123
|
throw new Error("baseToken and marketId are required.");
|
|
124
|
+
const baseToken = normalizeAddress(args.baseToken, "baseToken");
|
|
125
|
+
const marketId = String(args.marketId).trim();
|
|
126
|
+
if (!MARKET_ID_RE.test(marketId)) {
|
|
127
|
+
throw new Error(`marketId must be a 66-character config hash (0x + 64 hex). Received: ${marketId}`);
|
|
128
|
+
}
|
|
16
129
|
const { signer } = await resolveClient();
|
|
17
|
-
const raw = await createPool(
|
|
130
|
+
const raw = await createPool(baseToken, marketId);
|
|
18
131
|
const data = await finalizeMutationResult(raw, signer, "create_perp_market");
|
|
19
|
-
|
|
132
|
+
let onChainPool = null;
|
|
133
|
+
try {
|
|
134
|
+
const resolved = await getMarketPoolByBaseToken(marketId, baseToken, getChainId());
|
|
135
|
+
if (resolved?.poolId && !/^0x0{64}$/i.test(String(resolved.poolId))) {
|
|
136
|
+
onChainPool = resolved;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
onChainPool = null;
|
|
141
|
+
}
|
|
142
|
+
const payload = onChainPool
|
|
143
|
+
? { ...data, onChainPool }
|
|
144
|
+
: data;
|
|
145
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: payload }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
20
146
|
}
|
|
21
147
|
catch (error) {
|
|
22
|
-
|
|
148
|
+
const payload = buildCreateMarketErrorPayload(args, error);
|
|
149
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }], isError: true };
|
|
23
150
|
}
|
|
24
151
|
},
|
|
25
152
|
};
|
|
@@ -23,7 +23,7 @@ export const executeTradeTool = {
|
|
|
23
23
|
collateralAmount: z.union([z.string(), z.number()]).describe("Collateral. e.g. '100' or 'raw:100000000' (6 decimals for USDC)."),
|
|
24
24
|
size: z.union([z.string(), z.number()]).describe("Notional size in base tokens. e.g. '0.5' BTC or 'raw:50000000'."),
|
|
25
25
|
price: z.union([z.string(), z.number()]).describe("Execution or Limit price. e.g. '65000' or 'raw:...'"),
|
|
26
|
-
timeInForce: z.
|
|
26
|
+
timeInForce: z.union([z.number(), z.string()]).describe("SDK v1.0.2 supports IOC only. Use 0 or 'IOC'."),
|
|
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
29
|
executionFeeToken: z.string().optional().describe("Address of token to pay gas/execution fees (typically USDC). Default is pool quoteToken."),
|
|
@@ -50,6 +50,11 @@ export const executeTradeTool = {
|
|
|
50
50
|
const poolData = poolResponse?.data || (poolResponse?.marketId ? poolResponse : null);
|
|
51
51
|
if (!poolData)
|
|
52
52
|
throw new Error(`Could not find pool metadata for ID: ${poolId}`);
|
|
53
|
+
const resolvedMarketId = String(poolData.marketId ?? "").trim();
|
|
54
|
+
const requestedMarketId = String(args.marketId ?? "").trim();
|
|
55
|
+
if (resolvedMarketId && requestedMarketId && resolvedMarketId.toLowerCase() !== requestedMarketId.toLowerCase()) {
|
|
56
|
+
throw new Error(`Invalid marketId: marketId mismatch for poolId=${poolId}. Provided=${requestedMarketId}, resolved=${resolvedMarketId}.`);
|
|
57
|
+
}
|
|
53
58
|
const baseDecimals = Number(poolData.baseDecimals ?? 18);
|
|
54
59
|
const quoteDecimals = Number(poolData.quoteDecimals ?? 6);
|
|
55
60
|
const mappedDirection = mapDirection(args.direction);
|
|
@@ -1,5 +1,106 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
+
import { normalizeAddress } from "../utils/address.js";
|
|
4
|
+
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
5
|
+
function collectRows(input) {
|
|
6
|
+
if (Array.isArray(input))
|
|
7
|
+
return input.flatMap(collectRows);
|
|
8
|
+
if (!input || typeof input !== "object")
|
|
9
|
+
return [];
|
|
10
|
+
if (input.poolId || input.pool_id || input.marketId || input.market_id)
|
|
11
|
+
return [input];
|
|
12
|
+
return Object.values(input).flatMap(collectRows);
|
|
13
|
+
}
|
|
14
|
+
function isNonEmptyObject(value) {
|
|
15
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0;
|
|
16
|
+
}
|
|
17
|
+
async function findBaseDetailFromMarkets(client, chainId, baseAddress) {
|
|
18
|
+
let rows = [];
|
|
19
|
+
try {
|
|
20
|
+
const searchRes = await client.markets.searchMarket({ chainId, keyword: "", limit: 2000 });
|
|
21
|
+
rows = collectRows(searchRes?.data ?? searchRes);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
rows = [];
|
|
25
|
+
}
|
|
26
|
+
if (rows.length === 0) {
|
|
27
|
+
try {
|
|
28
|
+
const poolListRes = await client.api?.getPoolList?.();
|
|
29
|
+
rows = collectRows(poolListRes?.data ?? poolListRes);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
rows = [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const normalized = baseAddress.toLowerCase();
|
|
36
|
+
const matched = rows.find((row) => String(row?.baseToken ?? row?.baseAddress ?? "").trim().toLowerCase() === normalized);
|
|
37
|
+
if (!matched)
|
|
38
|
+
return null;
|
|
39
|
+
const poolId = String(matched?.poolId ?? matched?.pool_id ?? "").trim();
|
|
40
|
+
if (!poolId) {
|
|
41
|
+
return {
|
|
42
|
+
chainId,
|
|
43
|
+
baseAddress,
|
|
44
|
+
baseToken: baseAddress,
|
|
45
|
+
baseSymbol: matched?.baseSymbol ?? null,
|
|
46
|
+
baseDecimals: matched?.baseDecimals ?? matched?.base_decimals ?? null,
|
|
47
|
+
baseTokenIcon: matched?.baseTokenIcon ?? matched?.base_token_icon ?? null,
|
|
48
|
+
marketId: matched?.marketId ?? matched?.market_id ?? null,
|
|
49
|
+
poolId: null,
|
|
50
|
+
source: "market_search_fallback",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const detailRes = await client.markets.getMarketDetail({ chainId, poolId });
|
|
55
|
+
const detail = detailRes?.data || (detailRes?.marketId ? detailRes : null);
|
|
56
|
+
if (detail) {
|
|
57
|
+
return {
|
|
58
|
+
chainId,
|
|
59
|
+
baseAddress,
|
|
60
|
+
baseToken: detail.baseToken ?? baseAddress,
|
|
61
|
+
baseSymbol: detail.baseSymbol ?? matched?.baseSymbol ?? null,
|
|
62
|
+
baseDecimals: detail.baseDecimals ?? matched?.baseDecimals ?? matched?.base_decimals ?? null,
|
|
63
|
+
baseTokenIcon: detail.baseTokenIcon ?? matched?.baseTokenIcon ?? null,
|
|
64
|
+
quoteSymbol: detail.quoteSymbol ?? matched?.quoteSymbol ?? null,
|
|
65
|
+
marketId: detail.marketId ?? matched?.marketId ?? null,
|
|
66
|
+
poolId: detail.poolId ?? poolId,
|
|
67
|
+
source: "market_detail_fallback",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
chainId,
|
|
75
|
+
baseAddress,
|
|
76
|
+
baseToken: baseAddress,
|
|
77
|
+
baseSymbol: matched?.baseSymbol ?? null,
|
|
78
|
+
baseDecimals: matched?.baseDecimals ?? matched?.base_decimals ?? null,
|
|
79
|
+
baseTokenIcon: matched?.baseTokenIcon ?? matched?.base_token_icon ?? null,
|
|
80
|
+
quoteSymbol: matched?.quoteSymbol ?? null,
|
|
81
|
+
marketId: matched?.marketId ?? matched?.market_id ?? null,
|
|
82
|
+
poolId,
|
|
83
|
+
source: "market_search_fallback",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function buildReadErrorPayload(args, messageLike, code = "SDK_READ_ERROR") {
|
|
87
|
+
const chainId = args.chainId ?? getChainId();
|
|
88
|
+
const message = extractErrorMessage(messageLike, "Failed to read base token detail.");
|
|
89
|
+
return {
|
|
90
|
+
status: "error",
|
|
91
|
+
error: {
|
|
92
|
+
tool: "get_base_detail",
|
|
93
|
+
code,
|
|
94
|
+
message,
|
|
95
|
+
hint: "Check baseAddress validity and market availability on current chain.",
|
|
96
|
+
action: "Use list_pools/find_pool/get_pool_metadata to confirm base token, then retry.",
|
|
97
|
+
details: {
|
|
98
|
+
chainId,
|
|
99
|
+
baseAddress: args.baseAddress ?? null,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
3
104
|
export const getBaseDetailTool = {
|
|
4
105
|
name: "get_base_detail",
|
|
5
106
|
description: "[MARKET] Get base token details.",
|
|
@@ -11,11 +112,36 @@ export const getBaseDetailTool = {
|
|
|
11
112
|
try {
|
|
12
113
|
const { client } = await resolveClient();
|
|
13
114
|
const chainId = args.chainId ?? getChainId();
|
|
14
|
-
const
|
|
15
|
-
|
|
115
|
+
const baseAddress = normalizeAddress(args.baseAddress, "baseAddress");
|
|
116
|
+
const result = await client.markets.getBaseDetail({ chainId, baseAddress });
|
|
117
|
+
const hasCode = !!result && typeof result === "object" && !Array.isArray(result) && Object.prototype.hasOwnProperty.call(result, "code");
|
|
118
|
+
const code = hasCode ? Number(result.code) : 0;
|
|
119
|
+
const payload = hasCode ? result.data : result;
|
|
120
|
+
if (hasCode && Number.isFinite(code) && code !== 0) {
|
|
121
|
+
const body = buildReadErrorPayload({ ...args, baseAddress }, result.msg ?? result.message ?? result, "SDK_READ_ERROR");
|
|
122
|
+
return { content: [{ type: "text", text: JSON.stringify(body, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }], isError: true };
|
|
123
|
+
}
|
|
124
|
+
if (payload === null || payload === undefined) {
|
|
125
|
+
const fallback = await findBaseDetailFromMarkets(client, chainId, baseAddress);
|
|
126
|
+
if (fallback) {
|
|
127
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: fallback }, null, 2) }] };
|
|
128
|
+
}
|
|
129
|
+
const body = buildReadErrorPayload({ ...args, baseAddress }, "get_base_detail returned empty data.", "NOT_FOUND");
|
|
130
|
+
return { content: [{ type: "text", text: JSON.stringify(body, null, 2) }], isError: true };
|
|
131
|
+
}
|
|
132
|
+
if (typeof payload === "object" && !Array.isArray(payload) && !isNonEmptyObject(payload)) {
|
|
133
|
+
const fallback = await findBaseDetailFromMarkets(client, chainId, baseAddress);
|
|
134
|
+
if (fallback) {
|
|
135
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: fallback }, null, 2) }] };
|
|
136
|
+
}
|
|
137
|
+
const body = buildReadErrorPayload({ ...args, baseAddress }, "get_base_detail returned an empty object.", "NOT_FOUND");
|
|
138
|
+
return { content: [{ type: "text", text: JSON.stringify(body, null, 2) }], isError: true };
|
|
139
|
+
}
|
|
140
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: payload }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
|
|
16
141
|
}
|
|
17
142
|
catch (error) {
|
|
18
|
-
|
|
143
|
+
const body = buildReadErrorPayload(args, error, "TOOL_EXECUTION_ERROR");
|
|
144
|
+
return { content: [{ type: "text", text: JSON.stringify(body, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }], isError: true };
|
|
19
145
|
}
|
|
20
146
|
},
|
|
21
147
|
};
|
package/dist/tools/getOrders.js
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
3
|
import { getOrderTypeDesc, getOrderStatusDesc, getDirectionDesc, getHistoryOrderStatusDesc, getExecTypeDesc } from "../utils/mappings.js";
|
|
4
|
+
function pickFirstDefined(...values) {
|
|
5
|
+
for (const value of values) {
|
|
6
|
+
if (value !== undefined && value !== null && String(value).trim() !== "") {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
function toMaybeNumber(value) {
|
|
13
|
+
if (value === undefined || value === null || value === "")
|
|
14
|
+
return undefined;
|
|
15
|
+
const numeric = Number(value);
|
|
16
|
+
return Number.isFinite(numeric) ? numeric : undefined;
|
|
17
|
+
}
|
|
18
|
+
function resolveOpenOrderStatus(order) {
|
|
19
|
+
const statusRaw = toMaybeNumber(pickFirstDefined(order?.status, order?.orderStatus, order?.order_status));
|
|
20
|
+
if (statusRaw === undefined) {
|
|
21
|
+
return { statusRaw: undefined, statusDesc: "Open" };
|
|
22
|
+
}
|
|
23
|
+
return { statusRaw, statusDesc: getOrderStatusDesc(statusRaw) };
|
|
24
|
+
}
|
|
25
|
+
function resolveHistoryOrderStatus(order) {
|
|
26
|
+
const orderStatusRaw = toMaybeNumber(pickFirstDefined(order?.orderStatus, order?.order_status, order?.status));
|
|
27
|
+
return {
|
|
28
|
+
orderStatusRaw,
|
|
29
|
+
orderStatusDesc: getHistoryOrderStatusDesc(orderStatusRaw),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
4
32
|
export const getOrdersTool = {
|
|
5
33
|
name: "get_orders",
|
|
6
34
|
description: "[ACCOUNT] Get orders (open or history) with optional filters.",
|
|
@@ -19,7 +47,7 @@ export const getOrdersTool = {
|
|
|
19
47
|
results.open = (openRes?.data || []).map((order) => ({
|
|
20
48
|
...order,
|
|
21
49
|
orderTypeDesc: getOrderTypeDesc(order.orderType),
|
|
22
|
-
|
|
50
|
+
...resolveOpenOrderStatus(order),
|
|
23
51
|
directionDesc: getDirectionDesc(order.direction)
|
|
24
52
|
}));
|
|
25
53
|
if (args.poolId) {
|
|
@@ -32,7 +60,7 @@ export const getOrdersTool = {
|
|
|
32
60
|
results.history = (historyRes?.data || []).map((order) => ({
|
|
33
61
|
...order,
|
|
34
62
|
orderTypeDesc: getOrderTypeDesc(order.orderType),
|
|
35
|
-
|
|
63
|
+
...resolveHistoryOrderStatus(order),
|
|
36
64
|
directionDesc: getDirectionDesc(order.direction),
|
|
37
65
|
execTypeDesc: getExecTypeDesc(order.execType)
|
|
38
66
|
}));
|