@michaleffffff/mcp-trading-server 3.0.16 → 3.0.21

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.
@@ -1,7 +1,9 @@
1
1
  const SLIPPAGE_PCT_4DP_RE = /^\d+$/;
2
2
  export const SLIPPAGE_PCT_4DP_MAX = 10000n;
3
+ export const BUSINESS_SLIPPAGE_PCT_4DP_MAX = BigInt(process.env.BUSINESS_MAX_SLIPPAGE_PCT_4DP ?? "500");
3
4
  export const SLIPPAGE_PCT_4DP_DESC = "Slippage in 4-decimal precision raw units (1 = 0.01%, 10000 = 100%)";
4
5
  const SLIPPAGE_PERCENT_HUMAN_RE = /^\d+(\.\d{1,2})?$/;
6
+ export const BUSINESS_LP_SLIPPAGE_MAX_RATIO = Number(process.env.BUSINESS_MAX_LP_SLIPPAGE_RATIO ?? 0.05);
5
7
  export function isValidSlippagePct4dp(value) {
6
8
  if (!SLIPPAGE_PCT_4DP_RE.test(value))
7
9
  return false;
@@ -12,6 +14,10 @@ export function normalizeSlippagePct4dp(value, label = "slippagePct") {
12
14
  if (!isValidSlippagePct4dp(raw)) {
13
15
  throw new Error(`${label} must be an integer in [0, 10000] with 4-decimal precision (1 = 0.01%).`);
14
16
  }
17
+ const parsed = BigInt(raw);
18
+ if (parsed > BUSINESS_SLIPPAGE_PCT_4DP_MAX) {
19
+ throw new Error(`${label} exceeds business safety cap ${BUSINESS_SLIPPAGE_PCT_4DP_MAX.toString()} (${Number(BUSINESS_SLIPPAGE_PCT_4DP_MAX) / 100}%).`);
20
+ }
15
21
  return raw;
16
22
  }
17
23
  export function normalizeSlippagePct4dpFlexible(value, label = "slippagePct") {
@@ -34,5 +40,19 @@ export function normalizeSlippagePct4dpFlexible(value, label = "slippagePct") {
34
40
  if (converted > SLIPPAGE_PCT_4DP_MAX) {
35
41
  throw new Error(`${label} must be <= 100% (raw <= 10000).`);
36
42
  }
43
+ if (converted > BUSINESS_SLIPPAGE_PCT_4DP_MAX) {
44
+ throw new Error(`${label} exceeds business safety cap ${BUSINESS_SLIPPAGE_PCT_4DP_MAX.toString()} (${Number(BUSINESS_SLIPPAGE_PCT_4DP_MAX) / 100}%).`);
45
+ }
37
46
  return converted.toString();
38
47
  }
48
+ export function normalizeLpSlippageRatio(value, label = "slippage") {
49
+ const numeric = Number(value);
50
+ if (!Number.isFinite(numeric) || numeric < 0) {
51
+ throw new Error(`${label} must be a finite number >= 0.`);
52
+ }
53
+ const ratio = numeric > 1 && numeric <= 100 ? numeric / 100 : numeric;
54
+ if (ratio > BUSINESS_LP_SLIPPAGE_MAX_RATIO) {
55
+ throw new Error(`${label} exceeds business safety cap ${BUSINESS_LP_SLIPPAGE_MAX_RATIO * 100}%.`);
56
+ }
57
+ return ratio;
58
+ }
@@ -0,0 +1,15 @@
1
+ import { Contract } from "ethers";
2
+ const ERC20_DECIMALS_ABI = [
3
+ "function decimals() view returns (uint8)",
4
+ ];
5
+ export async function fetchErc20Decimals(providerOrSigner, tokenAddress, label = "token") {
6
+ if (!providerOrSigner) {
7
+ throw new Error(`Provider unavailable while resolving decimals for ${label}.`);
8
+ }
9
+ const contract = new Contract(tokenAddress, ERC20_DECIMALS_ABI, providerOrSigner);
10
+ const decimals = Number(await contract.decimals());
11
+ if (!Number.isFinite(decimals) || decimals < 0) {
12
+ throw new Error(`Invalid decimals returned for ${label}: ${decimals}`);
13
+ }
14
+ return Math.floor(decimals);
15
+ }
@@ -36,25 +36,17 @@ export function ensureUnits(value, decimals, label = "value", options = {}) {
36
36
  }
37
37
  if (!DECIMAL_RE.test(str))
38
38
  throw new Error(`${label} must be a numeric string.`);
39
- // Truncate decimals if they exceed the allowed precision to prevent parseUnits throwing
40
39
  if (str.includes(".")) {
41
- const parts = str.split(".");
42
- if (parts[1].length > decimals) {
43
- console.warn(`[ensureUnits] Truncating ${label} precision: ${str} -> ${parts[0]}.${parts[1].slice(0, decimals)}`);
44
- str = `${parts[0]}.${parts[1].slice(0, decimals)}`;
40
+ const [, fracPart = ""] = str.split(".");
41
+ if (fracPart.length > decimals) {
42
+ throw new Error(`${label} exceeds supported precision: got ${fracPart.length} decimals, max is ${decimals}.`);
45
43
  }
46
44
  }
47
45
  // Legacy compatibility: optionally treat very large integers as already raw.
48
46
  if (allowImplicitRaw && !str.includes(".") && (str.length > 12 || str.length > decimals)) {
49
47
  return str;
50
48
  }
51
- try {
52
- return parseUnits(str, decimals).toString();
53
- }
54
- catch (e) {
55
- console.warn(`[ensureUnits] parseUnits failed for ${label}: ${str}. Returning raw.`);
56
- return str;
57
- }
49
+ return parseUnits(str, decimals).toString();
58
50
  }
59
51
  export function parseUserUnits(value, decimals, label = "value") {
60
52
  let str = String(value).trim();
@@ -1,5 +1,28 @@
1
1
  import { getChainId } from "../auth/resolveClient.js";
2
2
  import { decodeErrorSelector } from "./errors.js";
3
+ import { getHistoryOrderStatusDesc } from "./mappings.js";
4
+ const FINAL_HISTORY_STATUSES = new Set([1, 2, 9]);
5
+ function collectHistoryRows(historyRes) {
6
+ if (Array.isArray(historyRes?.data))
7
+ return historyRes.data;
8
+ if (Array.isArray(historyRes?.data?.data))
9
+ return historyRes.data.data;
10
+ if (Array.isArray(historyRes))
11
+ return historyRes;
12
+ return [];
13
+ }
14
+ function normalizeHash(value) {
15
+ return String(value ?? "").trim().toLowerCase();
16
+ }
17
+ function enrichCancelReason(order) {
18
+ let cancelReason = order?.cancelReason || (Number(order?.status) === 1 ? "Unknown cancellation" : null);
19
+ if (cancelReason && cancelReason.startsWith("0x")) {
20
+ const decoded = decodeErrorSelector(cancelReason);
21
+ if (decoded)
22
+ cancelReason = `${cancelReason} (${decoded})`;
23
+ }
24
+ return cancelReason;
25
+ }
3
26
  /**
4
27
  * 等待后端索引并验证交易结果 (增强版)
5
28
  */
@@ -9,22 +32,26 @@ export async function verifyTradeOutcome(client, address, poolId, txHash) {
9
32
  const maxAttempts = 6;
10
33
  let currentDelay = 1000;
11
34
  let matchedOrder = null;
35
+ let matchedBy = null;
12
36
  for (let i = 0; i < maxAttempts; i++) {
13
37
  try {
14
38
  // 查询历史订单
15
39
  const historyRes = await client.order.getOrderHistory({
16
40
  chainId,
17
41
  poolId,
18
- limit: 10
42
+ limit: 50,
43
+ page: 1,
19
44
  }, address);
20
- const history = historyRes?.data || historyRes?.data?.data || [];
21
- matchedOrder = history.find((o) => String(o.orderHash).toLowerCase() === txHash.toLowerCase() ||
22
- String(o.txHash).toLowerCase() === txHash.toLowerCase());
45
+ const history = collectHistoryRows(historyRes);
46
+ const targetHash = normalizeHash(txHash);
47
+ const txHashMatch = history.find((o) => normalizeHash(o.txHash) === targetHash);
48
+ const orderHashMatch = history.find((o) => normalizeHash(o.orderHash) === targetHash);
49
+ matchedOrder = txHashMatch ?? orderHashMatch ?? null;
50
+ matchedBy = txHashMatch ? "txHash" : orderHashMatch ? "orderHash" : null;
23
51
  // 如果找到了订单且已经有最终状态,则退出轮询
24
52
  if (matchedOrder) {
25
53
  const status = Number(matchedOrder.status);
26
- if (status === 1 || status === 9 || status === 2) {
27
- // 1: Cancelled, 9: Successful, 2: Expired
54
+ if (FINAL_HISTORY_STATUSES.has(status)) {
28
55
  break;
29
56
  }
30
57
  }
@@ -46,16 +73,20 @@ export async function verifyTradeOutcome(client, address, poolId, txHash) {
46
73
  catch (e) {
47
74
  console.warn(`[verifyTradeOutcome] Failed to fetch positions:`, e);
48
75
  }
49
- let cancelReason = matchedOrder?.cancelReason || (matchedOrder?.status === 1 ? "Unknown cancellation" : null);
50
- if (cancelReason && cancelReason.startsWith("0x")) {
51
- const decoded = decodeErrorSelector(cancelReason);
52
- if (decoded)
53
- cancelReason = `${cancelReason} (${decoded})`;
54
- }
76
+ const statusCode = matchedOrder ? Number(matchedOrder.status) : null;
77
+ const statusText = statusCode === null || !Number.isFinite(statusCode)
78
+ ? null
79
+ : getHistoryOrderStatusDesc(statusCode);
80
+ const final = statusCode !== null && FINAL_HISTORY_STATUSES.has(statusCode);
81
+ const cancelReason = matchedOrder ? enrichCancelReason(matchedOrder) : null;
55
82
  return {
56
83
  order: matchedOrder,
57
84
  positions: positions,
58
85
  verified: !!matchedOrder,
86
+ matchedBy,
87
+ statusCode,
88
+ statusText,
89
+ final,
59
90
  cancelReason
60
91
  };
61
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michaleffffff/mcp-trading-server",
3
- "version": "3.0.16",
3
+ "version": "3.0.21",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "myx-mcp": "dist/server.js"