@michaleffffff/mcp-trading-server 3.0.29 → 3.1.0

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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.1.0 - 2026-03-20
4
+ ### Changed
5
+ - 发布 `3.1.0`,统一版本元数据到新的主次版本号。
6
+ - 运行环境现在只区分测试环境与正式环境,不再走 beta host / beta 地址分支。
7
+ - 修复测试环境下的市场发现链路,`list_pools` 恢复返回当前链池列表,`get_all_tickers` 在主接口不可用时继续走 fallback。
8
+
9
+ ## 3.0.31 - 2026-03-20
10
+ ### Fixed
11
+ - 修复 `normalizeSlippageRatio` 在处理 1% 到 100% 之间数值时的歧义问题。
12
+ - 为 ARB 池验证了 1% 滑点的存入操作(继续验证单参数调用的局限性)。
13
+
14
+ ## [3.0.30] - 2026-03-20
15
+ ### Fixed
16
+ - 完全对齐 SDK 流动性逻辑:移除 MCP 手动定义的预言机价格注入(适用于所有 State 0/1/2 池子)。
17
+ - 设置流动性操作默认滑点为 0.1%。
18
+ - 强制所有流动性交易调用 1 个参数的合约签名,以匹配 SDK v1.0.4-beta.4 的行为。
19
+
3
20
  ## [3.0.29] - 2026-03-20
4
21
  - **Fix**: Implemented oracle validation bypass for State 0 (Cook) and State 1 (Primed) pools in liquidity management.
5
22
  - This allows adding/removing liquidity on newly created pools that haven't initialized their oracle feeds yet.
package/README.md CHANGED
@@ -6,7 +6,7 @@ A production-ready MCP (Model Context Protocol) server for deep integration with
6
6
 
7
7
  # Release Notes
8
8
 
9
- - **Current release: 3.0.24**
9
+ - **Current release: 3.1.0**
10
10
  - **SDK baseline**: `@myx-trade/sdk@^1.0.4-beta.4` compatibility completed.
11
11
  - **Refinement**: Consolidated 40+ specialized tools into ~26 high-level unified tools.
12
12
  - **Improved UX**: Enhanced AI parameter parsing, automated unit conversion, and structured error reporting.
@@ -37,7 +37,6 @@ CHAIN_ID=...
37
37
  BROKER_ADDRESS=0x...
38
38
  QUOTE_TOKEN_ADDRESS=0x...
39
39
  QUOTE_TOKEN_DECIMALS=...
40
- IS_BETA_MODE=true
41
40
  ```
42
41
 
43
42
  ## Testnet `MYXBroker` Reference
@@ -46,7 +45,6 @@ IS_BETA_MODE=true
46
45
  - Linea test: `0x634EfDC9dC76D7AbF6E49279875a31B02E9891e2`
47
46
 
48
47
  Use the broker that matches your active RPC and chain configuration.
49
- If `IS_BETA_MODE` is omitted, MCP now auto-detects beta mode for the two testnet brokers above.
50
48
 
51
49
  ---
52
50
 
@@ -91,14 +89,13 @@ If `IS_BETA_MODE` is omitted, MCP now auto-detects beta mode for the two testnet
91
89
 
92
90
  - **Explicit execution price**: `open_position_simple` no longer auto-fills a fresh Oracle price for `MARKET` orders; provide `price` explicitly when opening a position.
93
91
  - **SDK-delegated funding delta**: MCP no longer performs its own increase-order margin/deposit reconciliation. New increase orders delegate collateral shortfall handling to SDK `createIncreaseOrder`.
94
- - **Beta broker compatibility**: current Arbitrum Sepolia / Linea Sepolia test brokers auto-enable beta mode when `IS_BETA_MODE` is unset.
95
92
  - **Exact approvals by default**: local fallback flows now prefer exact approvals instead of implicit unlimited approval.
96
93
  - **Size semantics**: `size` always means base-asset quantity, not USD notional.
97
94
  - **Pre-check semantics**: `check_account_ready` now uses SDK `getAvailableMarginBalance`. When that read fails, the response marks `summary.degraded=true` and includes diagnostics instead of silently trusting stale `freeMargin`.
98
95
  - **Direction validation**: when a tool operates on an existing `positionId`, the supplied `direction` must match the live position.
99
96
  - **TP/SL semantics**: LONG requires `tpPrice > entryPrice` and `slPrice < entryPrice`; SHORT uses the inverse.
100
97
  - **LP safety**: LP preview failures are fail-close and no longer downgrade to `minAmountOut=0`.
101
- - **LP slippage input**: `manage_liquidity.slippage` only validates numeric format and ratio normalization; MCP no longer imposes its own 5% business cap.
98
+ - **LP slippage input**: `manage_liquidity.slippage` now matches SDK semantics and must be a ratio in `(0, 1]`; MCP still does not impose its old 5% business cap.
102
99
  - **LP metadata safety**: `get_pool_metadata(includeLiquidity=true)` now ignores caller-supplied `marketPrice` and derives liquidity depth from a fresh Oracle price only.
103
100
  - **LP holdings semantics**: `get_my_lp_holdings` is an inventory view; returned rows are no longer ranked by mixed BASE/QUOTE LP raw balances.
104
101
  - **LP token-source safety**: `get_my_lp_holdings` now falls back to live `poolInfo.basePool.poolToken` / `poolInfo.quotePool.poolToken` when market-detail token addresses are stale or mismatched.
package/TOOL_EXAMPLES.md CHANGED
@@ -304,7 +304,7 @@ Alias-friendly form also works:
304
304
 
305
305
  LP preview failures now fail closed; the server no longer downgrades to `minAmountOut=0`.
306
306
  Oracle-backed LP pricing requires a fresh price snapshot before execution.
307
- `slippage` here is a ratio, so `0.01 = 1%` and `0.005 = 0.5%`.
307
+ `slippage` here must be a ratio in `(0, 1]`, so `0.01 = 1%` and `0.005 = 0.5%`.
308
308
  MCP no longer adds its own 5% LP slippage business cap on top of the provided value.
309
309
  When the SDK LP path fails on a non-standard token `allowance()` read, MCP falls back to the direct router path automatically.
310
310
 
@@ -2,10 +2,6 @@ import { MyxClient } from "@myx-trade/sdk";
2
2
  import { JsonRpcProvider, Wallet } from "ethers";
3
3
  import { normalizeAddress } from "../utils/address.js";
4
4
  let cached = null;
5
- const BETA_BROKERS_BY_CHAIN = {
6
- 421614: [],
7
- 59141: [],
8
- };
9
5
  function getDefaultBrokerByChainId(chainId) {
10
6
  // Testnet mappings
11
7
  if (chainId === 421614)
@@ -22,14 +18,6 @@ function getDefaultQuoteTokenByChainId(chainId) {
22
18
  return "0xD984fd34f91F92DA0586e1bE82E262fF27DC431b"; // Linea Sepolia
23
19
  return "0xD984fd34f91F92DA0586e1bE82E262fF27DC431b";
24
20
  }
25
- function resolveIsBetaMode(chainId, brokerAddressRaw) {
26
- const explicit = String(process.env.IS_BETA_MODE ?? "").trim().toLowerCase();
27
- if (explicit === "true")
28
- return true;
29
- if (explicit === "false")
30
- return false;
31
- return Boolean(BETA_BROKERS_BY_CHAIN[chainId]?.includes(String(brokerAddressRaw ?? "").trim().toLowerCase()));
32
- }
33
21
  export async function resolveClient() {
34
22
  if (cached)
35
23
  return cached;
@@ -50,7 +38,6 @@ export async function resolveClient() {
50
38
  throw new Error("QUOTE_TOKEN_ADDRESS env var is required.");
51
39
  const brokerAddress = normalizeAddress(brokerAddressRaw, "BROKER_ADDRESS");
52
40
  const quoteToken = normalizeAddress(quoteTokenRaw, "QUOTE_TOKEN_ADDRESS");
53
- const isBetaMode = resolveIsBetaMode(chainId, brokerAddress);
54
41
  const provider = new JsonRpcProvider(rpcUrl);
55
42
  const signer = new Wallet(privateKey, provider);
56
43
  // Inject the EIP-1193 mock so SDK can sign transactions seamlessly
@@ -75,7 +62,7 @@ export async function resolveClient() {
75
62
  signer: signer,
76
63
  brokerAddress,
77
64
  isTestnet,
78
- isBetaMode,
65
+ isBetaMode: false,
79
66
  walletClient: walletClient
80
67
  });
81
68
  cached = { client, address: signer.address, signer, chainId, quoteToken, quoteDecimals };
@@ -12,7 +12,7 @@ export const tradingGuidePrompt = {
12
12
  content: {
13
13
  type: "text",
14
14
  text: `
15
- # MYX Trading MCP Best Practices (v3.0.27)
15
+ # MYX Trading MCP Best Practices (v3.1.0)
16
16
 
17
17
  You are an expert crypto trader using the MYX Protocol. To ensure successful execution and safe handling of user funds, follow these patterns:
18
18
 
@@ -36,7 +36,6 @@ You are an expert crypto trader using the MYX Protocol. To ensure successful exe
36
36
  - **Execution Price**: \`open_position_simple\` no longer auto-fills a fresh Oracle price for \`MARKET\`; provide \`price\` explicitly when you want MCP to compute size / fee previews.
37
37
  - **Funding Delta Ownership**: MCP no longer performs its own increase-order margin/deposit reconciliation. SDK \`createIncreaseOrder\` owns deposit-delta handling for new increase orders.
38
38
  - **Pre-check Diagnostics**: \`check_account_ready\` now reports SDK \`availableMarginBalance\` first. If that read degrades, inspect \`summary.degraded\` and \`diagnostics.availableMarginError\` before trusting fallback account fields.
39
- - **Beta Broker Mode**: Current Arbitrum Sepolia / Linea Sepolia test brokers auto-enable beta mode when \`IS_BETA_MODE\` is omitted.
40
39
  - **Approval Safety**: Local fallback flows prefer exact approval sizing. Do not assume unlimited approvals are necessary.
41
40
  - **Position Semantics**: \`size\` is BASE quantity, not USD notional. If a \`positionId\` is supplied, \`direction\` must match the live position.
42
41
  - **TP/SL Semantics**: LONG should use \`tpPrice > entryPrice\` and \`slPrice < entryPrice\`; SHORT uses the inverse. Plain integer strings like \`"65000"\` are treated as human prices, not implicit raw 30-decimal values.
@@ -49,7 +48,6 @@ You are an expert crypto trader using the MYX Protocol. To ensure successful exe
49
48
  - Arbitrum test: \`0x895C4ae2A22bB26851011d733A9355f663a1F939\`
50
49
  - Linea test: \`0x634EfDC9dC76D7AbF6E49279875a31B02E9891e2\`
51
50
  - Always keep \`RPC_URL\`, \`CHAIN_ID\`, and \`BROKER_ADDRESS\` on the same network.
52
- - If \`IS_BETA_MODE\` is unset, MCP auto-detects beta mode for those two testnet brokers.
53
51
 
54
52
  Current Session:
55
53
  - Wallet: ${address}
package/dist/server.js CHANGED
@@ -461,7 +461,7 @@ function zodSchemaToJsonSchema(zodSchema) {
461
461
  };
462
462
  }
463
463
  // ─── MCP Server ───
464
- const server = new Server({ name: "myx-mcp-trading-server", version: "3.0.29" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
464
+ const server = new Server({ name: "myx-mcp-trading-server", version: "3.1.0" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
465
465
  // List tools
466
466
  server.setRequestHandler(ListToolsRequestSchema, async () => {
467
467
  return {
@@ -582,7 +582,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
582
582
  async function main() {
583
583
  const transport = new StdioServerTransport();
584
584
  await server.connect(transport);
585
- logger.info("🚀 MYX Trading MCP Server v3.0.24 running (stdio, pure on-chain, prod ready)");
585
+ logger.info("🚀 MYX Trading MCP Server v3.1.0 running (stdio, pure on-chain, prod ready)");
586
586
  }
587
587
  main().catch((err) => {
588
588
  logger.error("Fatal Server Startup Error", err);
@@ -31,10 +31,27 @@ function normalizeMarketState(value) {
31
31
  const casted = Number(value);
32
32
  return Number.isFinite(casted) ? casted : null;
33
33
  }
34
+ function getMarketStatePriority(state) {
35
+ if (state === 2)
36
+ return 0;
37
+ if (state === 1)
38
+ return 1;
39
+ if (state === 0)
40
+ return 2;
41
+ if (state === null)
42
+ return 3;
43
+ return 4;
44
+ }
34
45
  function normalizePoolId(row) {
35
46
  const poolId = row?.poolId ?? row?.pool_id ?? "";
36
47
  return String(poolId);
37
48
  }
49
+ function matchesChainId(row, chainId) {
50
+ const raw = row?.chainId ?? row?.chain_id;
51
+ if (raw === undefined || raw === null || raw === "")
52
+ return true;
53
+ return Number(raw) === chainId;
54
+ }
38
55
  function matchesKeyword(row, keywordUpper) {
39
56
  const poolId = normalizePoolId(row);
40
57
  const haystack = [
@@ -56,7 +73,7 @@ async function fetchApiMarketRows(client, chainId) {
56
73
  // Preferred path: documented markets.searchMarket
57
74
  try {
58
75
  const searchRes = await client.markets.searchMarket({ chainId, keyword: "", limit: 2000 });
59
- const searchRows = extractMarketRows(searchRes, chainId).filter((row) => normalizePoolId(row));
76
+ const searchRows = extractMarketRows(searchRes, chainId).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
60
77
  if (searchRows.length > 0)
61
78
  return searchRows;
62
79
  }
@@ -65,7 +82,7 @@ async function fetchApiMarketRows(client, chainId) {
65
82
  // Secondary path: documented markets.getPoolSymbolAll
66
83
  try {
67
84
  const symbolsRes = await client.markets.getPoolSymbolAll();
68
- const symbolRows = collectRows(symbolsRes?.data ?? symbolsRes).filter((row) => normalizePoolId(row));
85
+ const symbolRows = collectRows(symbolsRes?.data ?? symbolsRes).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
69
86
  if (symbolRows.length > 0)
70
87
  return symbolRows;
71
88
  }
@@ -73,12 +90,12 @@ async function fetchApiMarketRows(client, chainId) {
73
90
  }
74
91
  // Legacy fallback: internal api namespace (for backward compatibility)
75
92
  const marketListRes = await client.api?.getMarketList?.().catch(() => null);
76
- const marketRows = extractMarketRows(marketListRes, chainId);
93
+ const marketRows = extractMarketRows(marketListRes, chainId).filter((row) => matchesChainId(row, chainId));
77
94
  const marketRowsWithPoolId = marketRows.filter((row) => normalizePoolId(row));
78
95
  if (marketRowsWithPoolId.length > 0)
79
96
  return marketRowsWithPoolId;
80
97
  const poolListRes = await client.api?.getPoolList?.().catch(() => null);
81
- return collectRows(poolListRes?.data ?? poolListRes).filter((row) => normalizePoolId(row));
98
+ return collectRows(poolListRes?.data ?? poolListRes).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
82
99
  }
83
100
  export async function getMarketPrice(client, poolId, chainIdOverride) {
84
101
  const chainId = chainIdOverride ?? getChainId();
@@ -163,17 +180,23 @@ export async function searchMarket(client, keyword, limit = 1000, chainIdOverrid
163
180
  dedupedByPoolId.set(poolId, row);
164
181
  }
165
182
  }
166
- const activeMarkets = Array.from(dedupedByPoolId.values())
167
- .filter((row) => {
168
- const state = normalizeMarketState(row?.state ?? row?.poolState);
169
- return state === null || state === 2;
183
+ const orderedMarkets = Array.from(dedupedByPoolId.values())
184
+ .sort((left, right) => {
185
+ const leftState = normalizeMarketState(left?.state ?? left?.poolState);
186
+ const rightState = normalizeMarketState(right?.state ?? right?.poolState);
187
+ const byState = getMarketStatePriority(leftState) - getMarketStatePriority(rightState);
188
+ if (byState !== 0)
189
+ return byState;
190
+ const leftSymbol = String(left?.baseQuoteSymbol ?? left?.symbolName ?? left?.poolId ?? "");
191
+ const rightSymbol = String(right?.baseQuoteSymbol ?? right?.symbolName ?? right?.poolId ?? "");
192
+ return leftSymbol.localeCompare(rightSymbol);
170
193
  })
171
194
  .slice(0, requestedLimit);
172
195
  // Get tickers for these pools to get price and change24h
173
196
  let tickers = [];
174
- if (activeMarkets.length > 0) {
197
+ if (orderedMarkets.length > 0) {
175
198
  try {
176
- const poolIds = activeMarkets.map((market) => normalizePoolId(market));
199
+ const poolIds = orderedMarkets.map((market) => normalizePoolId(market));
177
200
  const tickerRes = await client.markets.getTickerList({ chainId, poolIds });
178
201
  tickers = Array.isArray(tickerRes) ? tickerRes : (tickerRes?.data || []);
179
202
  }
@@ -181,7 +204,7 @@ export async function searchMarket(client, keyword, limit = 1000, chainIdOverrid
181
204
  console.error("Failed to fetch tickers:", e);
182
205
  }
183
206
  }
184
- return activeMarkets.map((market) => {
207
+ return orderedMarkets.map((market) => {
185
208
  const poolId = normalizePoolId(market);
186
209
  const state = normalizeMarketState(market?.state ?? market?.poolState);
187
210
  const ticker = tickers.find((t) => String(t.poolId).toLowerCase() === poolId.toLowerCase());
@@ -213,7 +236,7 @@ export async function getPoolList(client, chainIdOverride) {
213
236
  const chainId = chainIdOverride ?? getChainId();
214
237
  try {
215
238
  const searchRes = await client.markets.searchMarket({ chainId, keyword: "", limit: 2000 });
216
- const rows = extractMarketRows(searchRes, chainId).filter((row) => normalizePoolId(row));
239
+ const rows = extractMarketRows(searchRes, chainId).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
217
240
  if (rows.length > 0)
218
241
  return rows;
219
242
  }
@@ -221,13 +244,14 @@ export async function getPoolList(client, chainIdOverride) {
221
244
  }
222
245
  try {
223
246
  const symbolsRes = await client.markets.getPoolSymbolAll();
224
- const rows = collectRows(symbolsRes?.data ?? symbolsRes).filter((row) => normalizePoolId(row));
247
+ const rows = collectRows(symbolsRes?.data ?? symbolsRes).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
225
248
  if (rows.length > 0)
226
249
  return rows;
227
250
  }
228
251
  catch {
229
252
  }
230
- return client.api?.getPoolList?.();
253
+ const poolListRes = await client.api?.getPoolList?.();
254
+ return collectRows(poolListRes?.data ?? poolListRes).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
231
255
  }
232
256
  /**
233
257
  * 获取池子分级配置
@@ -279,9 +303,8 @@ export async function resolvePool(client, poolId, keyword, chainIdOverride) {
279
303
  };
280
304
  const rows = collect(poolListRes.data ?? poolListRes);
281
305
  const searchKey = (kw || pid || "").toUpperCase();
282
- const match = rows.find((row) => {
283
- if (Number(row?.state) !== 2)
284
- return false;
306
+ const match = rows
307
+ .filter((row) => {
285
308
  const base = String(row?.baseSymbol ?? "").toUpperCase();
286
309
  const pair = String(row?.baseQuoteSymbol ?? "").toUpperCase();
287
310
  const id = String(row?.poolId ?? row?.pool_id ?? "").toUpperCase();
@@ -292,7 +315,12 @@ export async function resolvePool(client, poolId, keyword, chainIdOverride) {
292
315
  id === searchKey ||
293
316
  baseToken === searchKey ||
294
317
  quoteToken === searchKey;
295
- });
318
+ })
319
+ .sort((left, right) => {
320
+ const leftState = normalizeMarketState(left?.state ?? left?.poolState);
321
+ const rightState = normalizeMarketState(right?.state ?? right?.poolState);
322
+ return getMarketStatePriority(leftState) - getMarketStatePriority(rightState);
323
+ })[0];
296
324
  if (match) {
297
325
  return String(match.poolId ?? match.pool_id);
298
326
  }
@@ -8,31 +8,25 @@ import { logger } from "../utils/logger.js";
8
8
  import { assertOracleFreshness, getFreshOraclePrice } from "./marketService.js";
9
9
  const LP_DECIMALS = 18;
10
10
  const POOL_MANAGER_BY_CHAIN = {
11
- 421614: "0xf268D9FeD3Bd56fd9aBdb4FeEb993338613678A8",
11
+ 421614: "0xB131655F326E82753b0e76c1ce853E257524f3a4",
12
12
  59141: "0x85e869d98216221807A06636541Ec93C9c0a4B0c",
13
13
  97: "0x4F917ef137b573D9790b87e3cF6dfb698cF00c9c",
14
14
  56: "0x13F2130c2F3bfd612BBCBF35FB9E467dd32bAF3A",
15
15
  };
16
- const POOL_MANAGER_BY_CHAIN_BETA = {
17
- 421614: "0x05314a21Fc97B74f168730153b2B63A870D25dE5",
18
- 59141: "0xcf51a6895864c6D8E507fC31EF16b9011287c5f4",
19
- 97: "0x9E84a999e15CCdb2F64a5AF10939c25769dF6b07",
20
- 56: "0x9E84a999e15CCdb2F64a5AF10939c25769dF6b07",
21
- };
22
16
  const POOL_MANAGER_ABI = [
23
17
  "function deployPool((bytes32 marketId,address baseToken))",
24
18
  "function getMarketPool(bytes32 marketId,address asset) view returns ((bytes32 marketId,bytes32 poolId,address baseToken,address quoteToken,uint8 riskTier,uint8 state,address basePoolToken,address quotePoolToken,uint16 maxPriceDeviation,bool compoundEnabled,uint64 windowCapUsd,address poolVault,address tradingVault))",
25
19
  ];
26
20
  const LP_ROUTER_BY_CHAIN = {
27
21
  421614: {
28
- router: "0x5E16C0699de2Ee5D69a620AB38AE2c81A29a58df",
29
- basePool: "0xDb41D850e9b30fc0417F488D1aB64fdc1d0DdFdc",
30
- quotePool: "0xF0784c1023C051Bf1D5b8470A896c536039B06B9",
22
+ router: "0x0fb875c10fe2fF981e467765E3daaE4355b180D0",
23
+ basePool: "0xeC0b3C76cC1C47f9B29313c706c1F6FD8D1C023f",
24
+ quotePool: "0x608Bfd6B8Df0807aA6d1500B76a3eAb3C9DFd728",
31
25
  },
32
26
  59141: {
33
- router: "0xf1395643D77DD839E4b8d3Bb122fd0275Cf07631",
34
- basePool: "0x31b093BBADbb2A3Bcd7E433E8Ed5bcdD76fFAFFe",
35
- quotePool: "0xFF8A6797E2063A7a4cd1468eB7cC0db9798C753f",
27
+ router: "0xc5331ab0159379E6CfCC1f09b63360D0B9715D74",
28
+ basePool: "0x8440a78E65F07D8013dd5B0640E0E713c8fd9893",
29
+ quotePool: "0x50F1F7A772672FE0637cF89AD3aFc093584eD040",
36
30
  },
37
31
  97: {
38
32
  router: "0xe9F2E58562aD1D50AfB1eD92EAa6D367A6D0e552",
@@ -45,28 +39,6 @@ const LP_ROUTER_BY_CHAIN = {
45
39
  quotePool: "0xB1E6df749A602892FafB27bb39Fd4F044527121E",
46
40
  },
47
41
  };
48
- const LP_ROUTER_BY_CHAIN_BETA = {
49
- 421614: {
50
- router: "0xfb790ECE13Cd9e296b4a06ABF38D10431360c236",
51
- basePool: "0x1F767BEa83EDe5E0C18904f439C579f52c2c0F1b",
52
- quotePool: "0x50ad7da312c58c6689bEF028954a366cb5b8ee13",
53
- },
54
- 59141: {
55
- router: "0x0AE31989318565620c03d35889a0B2536b8fba5C",
56
- basePool: "0xc9e3826c42183207B418116A16827C1605132c51",
57
- quotePool: "0x9A9934D3b22103dE822d94A22A3177d728FE0e5a",
58
- },
59
- 97: {
60
- router: "0x0F4C6f18Fb136DD1eBd6Da3C5d86a86597CF79a3",
61
- basePool: "0x51B62554a76197d5DF2D5dC4D57FF54d40775938",
62
- quotePool: "0x783Ed065a12e1C1D33c2a8d6408385C1843D3084",
63
- },
64
- 56: {
65
- router: "0x0F4C6f18Fb136DD1eBd6Da3C5d86a86597CF79a3",
66
- basePool: "0x51B62554a76197d5DF2D5dC4D57FF54d40775938",
67
- quotePool: "0x783Ed065a12e1C1D33c2a8d6408385C1843D3084",
68
- },
69
- };
70
42
  const PREVIEW_POOL_ABI = [
71
43
  "function previewLpAmountOut(bytes32,uint256,uint256) view returns (uint256)",
72
44
  "function previewQuoteAmountOut(bytes32,uint256,uint256) view returns (uint256)",
@@ -165,24 +137,19 @@ async function withMutedSdkAbiMismatchLogs(runner) {
165
137
  console.error = original;
166
138
  }
167
139
  }
168
- function isBetaModeEnabled() {
169
- return String(process.env.IS_BETA_MODE ?? "").trim().toLowerCase() === "true";
170
- }
171
140
  function getPoolManagerAddress(chainId) {
172
141
  const envAddress = String(process.env.POOL_MANAGER_ADDRESS ?? "").trim();
173
142
  if (envAddress) {
174
143
  return normalizeAddress(envAddress, "POOL_MANAGER_ADDRESS");
175
144
  }
176
- const source = isBetaModeEnabled() ? POOL_MANAGER_BY_CHAIN_BETA : POOL_MANAGER_BY_CHAIN;
177
- const mapped = source[chainId];
145
+ const mapped = POOL_MANAGER_BY_CHAIN[chainId];
178
146
  if (!mapped) {
179
- throw new Error(`Pool manager address is not configured for chainId=${chainId} (beta=${isBetaModeEnabled()}). Set POOL_MANAGER_ADDRESS env var.`);
147
+ throw new Error(`Pool manager address is not configured for chainId=${chainId}. Set POOL_MANAGER_ADDRESS env var.`);
180
148
  }
181
149
  return mapped;
182
150
  }
183
151
  function getLpAddresses(chainId) {
184
- const source = isBetaModeEnabled() ? LP_ROUTER_BY_CHAIN_BETA : LP_ROUTER_BY_CHAIN;
185
- const matched = source[chainId];
152
+ const matched = LP_ROUTER_BY_CHAIN[chainId];
186
153
  if (!matched) {
187
154
  throw new Error(`Liquidity router config not found for chainId=${chainId}.`);
188
155
  }
@@ -191,11 +158,11 @@ function getLpAddresses(chainId) {
191
158
  function normalizeSlippageRatio(slippage) {
192
159
  if (!Number.isFinite(slippage) || slippage < 0)
193
160
  return 0;
194
- if (slippage > 1 && slippage <= 100)
195
- return slippage / 100;
196
- if (slippage > 1)
197
- return 1;
198
- return slippage;
161
+ if (slippage > 100)
162
+ return slippage / 10000; // assume bps e.g. 200 = 2%
163
+ if (slippage >= 1)
164
+ return slippage / 100; // assume percent e.g. 1.5 = 1.5%
165
+ return slippage; // assume ratio e.g. 0.01 = 1%
199
166
  }
200
167
  function applyMinOutBySlippage(amountOut, slippage) {
201
168
  if (amountOut <= 0n)
@@ -327,6 +294,7 @@ async function executeLiquidityTxViaRouter(params) {
327
294
  const amountOut = await previewAmountOutForLiquidity(signer, chainId, poolId, poolType, action, amountIn, oraclePayload.referencePrice30);
328
295
  const minAmountOut = applyMinOutBySlippage(amountOut, slippage);
329
296
  const routerContract = new Contract(addresses.router, ROUTER_ABI, signer);
297
+ const oraclePriceTuples = oraclePayload.prices;
330
298
  const txOverrides = {};
331
299
  if (oraclePayload.value > 0n) {
332
300
  txOverrides.value = oraclePayload.value;
@@ -340,21 +308,16 @@ async function executeLiquidityTxViaRouter(params) {
340
308
  address,
341
309
  [],
342
310
  ];
343
- if (oraclePayload.prices.length > 0) {
344
- tx = await routerContract["depositQuote((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))"](oraclePayload.prices, paramsTuple, txOverrides);
345
- }
346
- else {
347
- tx = await routerContract["depositQuote((bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))"](paramsTuple, txOverrides);
348
- }
311
+ tx = await routerContract["depositQuote((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))"](oraclePriceTuples, paramsTuple, txOverrides);
349
312
  }
350
313
  else if (poolType === "QUOTE" && action === "withdraw") {
351
- const paramsTuple = [poolId, amountIn, minAmountOut, address];
352
- if (oraclePayload.prices.length > 0) {
353
- tx = await routerContract["withdrawQuote((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address))"](oraclePayload.prices, paramsTuple, txOverrides);
354
- }
355
- else {
356
- tx = await routerContract["withdrawQuote((bytes32,uint256,uint256,address))"](paramsTuple, txOverrides);
357
- }
314
+ const paramsTuple = [
315
+ poolId,
316
+ amountIn,
317
+ minAmountOut,
318
+ address,
319
+ ];
320
+ tx = await routerContract["withdrawQuote((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address))"](oraclePriceTuples, paramsTuple, txOverrides);
358
321
  }
359
322
  else if (poolType === "BASE" && action === "deposit") {
360
323
  const paramsTuple = [
@@ -364,21 +327,16 @@ async function executeLiquidityTxViaRouter(params) {
364
327
  address,
365
328
  [],
366
329
  ];
367
- if (oraclePayload.prices.length > 0) {
368
- tx = await routerContract["depositBase((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))"](oraclePayload.prices, paramsTuple, txOverrides);
369
- }
370
- else {
371
- tx = await routerContract["depositBase((bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))"](paramsTuple, txOverrides);
372
- }
330
+ tx = await routerContract["depositBase((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))"](oraclePriceTuples, paramsTuple, txOverrides);
373
331
  }
374
332
  else {
375
- const paramsTuple = [poolId, amountIn, minAmountOut, address];
376
- if (oraclePayload.prices.length > 0) {
377
- tx = await routerContract["withdrawBase((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address))"](oraclePayload.prices, paramsTuple, txOverrides);
378
- }
379
- else {
380
- tx = await routerContract["withdrawBase((bytes32,uint256,uint256,address))"](paramsTuple, txOverrides);
381
- }
333
+ const paramsTuple = [
334
+ poolId,
335
+ amountIn,
336
+ minAmountOut,
337
+ address,
338
+ ];
339
+ tx = await routerContract["withdrawBase((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address))"](oraclePriceTuples, paramsTuple, txOverrides);
382
340
  }
383
341
  const txHash = String(tx?.hash ?? "").trim();
384
342
  if (!txHash || !txHash.startsWith("0x")) {
@@ -15,7 +15,7 @@ export const getAllTickersTool = {
15
15
  // Fallback for networks/environments where getAllTickers endpoint is unavailable.
16
16
  const chainId = getChainId();
17
17
  const poolList = await getPoolList(client, chainId);
18
- const pools = Array.isArray(poolList?.data) ? poolList.data : (Array.isArray(poolList) ? poolList : []);
18
+ const pools = Array.isArray(poolList) ? poolList : (Array.isArray(poolList?.data) ? poolList.data : []);
19
19
  const poolIds = pools.map((p) => p?.poolId ?? p?.pool_id).filter((id) => !!id);
20
20
  if (poolIds.length === 0) {
21
21
  throw new Error("Failed to fetch all tickers and no pools were available for fallback query.");
@@ -228,7 +228,8 @@ export const getMyLpHoldingsTool = {
228
228
  let quoteCandidates = collectAddressCandidates(quotePoolToken);
229
229
  let baseResolved = await resolveLpBalanceFromCandidates(provider, address, baseCandidates);
230
230
  let quoteResolved = await resolveLpBalanceFromCandidates(provider, address, quoteCandidates);
231
- const needPoolInfoEnrichment = (BigInt(baseResolved.balanceRaw) === 0n && BigInt(quoteResolved.balanceRaw) === 0n) ||
231
+ const needPoolInfoEnrichment = BigInt(baseResolved.balanceRaw) === 0n ||
232
+ BigInt(quoteResolved.balanceRaw) === 0n ||
232
233
  !baseResolved.tokenAddress ||
233
234
  !quoteResolved.tokenAddress;
234
235
  if (needPoolInfoEnrichment) {
@@ -21,7 +21,8 @@ export const listPoolsTool = {
21
21
  getPoolList(client),
22
22
  client.markets.getPoolSymbolAll().catch(() => ({ data: [] }))
23
23
  ]);
24
- const poolsRaw = collectRows(poolListRes?.data ?? poolListRes);
24
+ const poolsSource = Array.isArray(poolListRes) ? poolListRes : poolListRes?.data ?? poolListRes;
25
+ const poolsRaw = collectRows(poolsSource);
25
26
  const symbolsRaw = collectRows(symbolsRes?.data ?? symbolsRes);
26
27
  const symbolMap = new Map(symbolsRaw
27
28
  .filter((row) => row?.poolId || row?.pool_id)
@@ -69,7 +69,7 @@ export const manageLiquidityTool = {
69
69
  poolType: z.enum(["BASE", "QUOTE"]).describe("'BASE' or 'QUOTE'"),
70
70
  poolId: z.string().describe("Pool ID or Base Token Address"),
71
71
  amount: z.coerce.string().describe("Amount in human-readable units string"),
72
- slippage: z.coerce.number().min(0).describe("LP slippage ratio (e.g. 0.01 = 1%)"),
72
+ slippage: z.coerce.number().gt(0).max(1).describe("LP slippage ratio in (0, 1], e.g. 0.01 = 1%"),
73
73
  chainId: z.coerce.number().int().positive().optional().describe("Optional chainId override"),
74
74
  },
75
75
  handler: async (args) => {
@@ -46,8 +46,8 @@ export function normalizeSlippagePct4dpFlexible(value, label = "slippagePct") {
46
46
  }
47
47
  export function normalizeLpSlippageRatio(value, label = "slippage") {
48
48
  const numeric = Number(value);
49
- if (!Number.isFinite(numeric) || numeric < 0) {
50
- throw new Error(`${label} must be a finite number >= 0.`);
49
+ if (!Number.isFinite(numeric) || numeric <= 0 || numeric > 1) {
50
+ throw new Error(`${label} must be a finite ratio in (0, 1].`);
51
51
  }
52
- return numeric > 1 && numeric <= 100 ? numeric / 100 : numeric;
52
+ return numeric;
53
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michaleffffff/mcp-trading-server",
3
- "version": "3.0.29",
3
+ "version": "3.1.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "myx-mcp": "dist/server.js"