@michaleffffff/mcp-trading-server 3.0.7 β 3.0.10
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 +51 -0
- package/README.md +2 -2
- package/TOOL_EXAMPLES.md +13 -0
- package/dist/server.js +2 -2
- package/dist/services/poolService.js +430 -6
- package/dist/tools/createPerpMarket.js +132 -5
- package/dist/tools/getBaseDetail.js +43 -3
- package/dist/tools/getOrders.js +30 -2
- package/dist/tools/manageTpSl.js +162 -25
- package/dist/utils/injectProvider.js +1 -0
- package/dist/utils/mappings.js +12 -7
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.10 - 2026-03-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Hardened `manage_tp_sl` delete behavior (`tpPrice=0` + `slPrice=0`):
|
|
7
|
+
- Added a unified cancellation path helper to always map delete intent to `cancelAllOrders` (by `orderId` or by `positionId`).
|
|
8
|
+
- Added fallback recovery for SDK/contract `InvalidParameter` reverts (including selector `0x613970e0`) so zero-price delete intent will still downgrade to explicit cancellation instead of failing.
|
|
9
|
+
|
|
10
|
+
## 3.0.9 - 2026-03-18
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Fixed `create_perp_market` observability and compatibility:
|
|
14
|
+
- Added strict `marketId` (66-char hash) and `baseToken` address validation.
|
|
15
|
+
- Added structured error payloads with decoded contract selector hints (e.g. `PoolExists(PoolId)`).
|
|
16
|
+
- Added fallback for SDK v1.0.2 write-path incompatibility (`deployPool is not a function`) by directly calling `PoolManager.deployPool`.
|
|
17
|
+
- Fixed `manage_tp_sl` delete semantics:
|
|
18
|
+
- `tpPrice=0` + `slPrice=0` now maps to explicit TP/SL order cancellation (by `orderId` or by `positionId`) instead of sending invalid on-chain TP/SL orders.
|
|
19
|
+
- Fixed `get_base_detail` null-read behavior:
|
|
20
|
+
- `success + null` is now returned as a structured error (`NOT_FOUND`) with chain/base context.
|
|
21
|
+
- Fixed `get_orders` OPEN status mapping:
|
|
22
|
+
- Removed `Unknown(undefined)` and normalized missing OPEN status to `Open`.
|
|
23
|
+
- Fixed LP SDK wait incompatibility:
|
|
24
|
+
- Added submitted tx-hash recovery path when SDK returns `...wait is not a function` after broadcast (e.g., BASE withdraw).
|
|
25
|
+
- Hardened direct LP router fallback:
|
|
26
|
+
- Added token allowance auto-approval before fallback deposit.
|
|
27
|
+
|
|
28
|
+
### Verified (Real Funds, Arbitrum Sepolia 421614)
|
|
29
|
+
- Deployed and minted new base token to active wallet:
|
|
30
|
+
- token: `0xDae49922Ff1699CA2A6cc4eE835B2c5a9f3Fe870`
|
|
31
|
+
- deploy tx: `0x99e69d66cac1b3a033281bae45dd421bba37794e1a80c1a12eddded99c48acce`
|
|
32
|
+
- Created new perp pool for the token (marketId: `0x2a3fee38e8beba148141bea5cab0bcbbb0cf24fd5509117346991cc438cb2fe6`):
|
|
33
|
+
- create tx: `0x97266886c673cee13531837bcf9a0524034bd85a018036f3557c4d126fef3771`
|
|
34
|
+
- derived poolId: `0x6c1a8af5123a0cf636293aff3fce2ea6addd4bce172c39c6467d4dc95ac3f83e`
|
|
35
|
+
- BASE LP add/remove validated on the new pool:
|
|
36
|
+
- add approval tx: `0xa2739346558f43f474953c7c93be05f0c66f2659f80d8b07804823f03926bdb0`
|
|
37
|
+
- add tx: `0x6a10d7dc5d2794643b10bc2b53080bfa8df1c99eedf35cd48a46edaa6d97d832`
|
|
38
|
+
- remove tx: `0x4148585452d259f6a9928404c7b8d324013e7ee13859392302ab78aa34fab2bc`
|
|
39
|
+
|
|
40
|
+
## 3.0.8 - 2026-03-18
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- Fixed `manage_liquidity` QUOTE pool ABI mismatch (`Expected length params=1, values=2`) by adding a safe fallback path:
|
|
44
|
+
- Keep SDK LP call as primary path.
|
|
45
|
+
- On SDK ABI-overload mismatch, switch to explicit-signature router transaction path (`depositQuote`/`withdrawQuote`) to avoid overloaded function ambiguity.
|
|
46
|
+
- Added the same overload-mismatch fallback for BASE pool LP operations (`depositBase`/`withdrawBase`) to prevent the same class of failure.
|
|
47
|
+
- Added SDK ABI-mismatch log suppression for LP calls to avoid noisy stack traces when fallback is activated.
|
|
48
|
+
|
|
49
|
+
### Verified
|
|
50
|
+
- Executed a real QUOTE remove liquidity transaction via MCP after the fix:
|
|
51
|
+
- txHash: `0x69e089b805cccd3b14d0c511309a0ce2aecf988344ec23ae27df929ad99af390`
|
|
52
|
+
- status: success (confirmed on-chain)
|
|
53
|
+
|
|
3
54
|
## 3.0.7 - 2026-03-18
|
|
4
55
|
|
|
5
56
|
### Changed
|
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.
|
|
9
|
+
- **Current release: 3.0.10**
|
|
10
10
|
- **SDK baseline**: `@myx-trade/sdk@^1.0.2` 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.
|
|
@@ -54,7 +54,7 @@ QUOTE_TOKEN_DECIMALS=18
|
|
|
54
54
|
* **`close_position`**: Strategy-based closing of specific positions.
|
|
55
55
|
* **`close_all_positions`**: Emergency exit for all positions in a specific pool.
|
|
56
56
|
* **`cancel_orders`**: Unified cancellation (Single ID, Pool-wide, or Account-wide).
|
|
57
|
-
* **`manage_tp_sl`**: Adjust protection orders for active positions or pending orders.
|
|
57
|
+
* **`manage_tp_sl`**: Adjust protection orders for active positions or pending orders. Deletion is supported via `tpPrice=0` + `slPrice=0`.
|
|
58
58
|
* **`adjust_margin`**: Add or remove collateral to manage liquidation risk.
|
|
59
59
|
|
|
60
60
|
### π Account & Portfolio
|
package/TOOL_EXAMPLES.md
CHANGED
|
@@ -60,6 +60,19 @@ Update existing protection orders or set new ones for a position.
|
|
|
60
60
|
}
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
Delete both TP/SL for a position:
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"name": "manage_tp_sl",
|
|
67
|
+
"arguments": {
|
|
68
|
+
"poolId": "BTC",
|
|
69
|
+
"positionId": "0xABC...",
|
|
70
|
+
"tpPrice": "0",
|
|
71
|
+
"slPrice": "0"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
63
76
|
---
|
|
64
77
|
|
|
65
78
|
## π΅ Market Data
|
package/dist/server.js
CHANGED
|
@@ -370,7 +370,7 @@ function zodSchemaToJsonSchema(zodSchema) {
|
|
|
370
370
|
};
|
|
371
371
|
}
|
|
372
372
|
// βββ MCP Server βββ
|
|
373
|
-
const server = new Server({ name: "myx-mcp-trading-server", version: "3.0.
|
|
373
|
+
const server = new Server({ name: "myx-mcp-trading-server", version: "3.0.10" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
|
|
374
374
|
// List tools
|
|
375
375
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
376
376
|
return {
|
|
@@ -491,7 +491,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
491
491
|
async function main() {
|
|
492
492
|
const transport = new StdioServerTransport();
|
|
493
493
|
await server.connect(transport);
|
|
494
|
-
logger.info("π MYX Trading MCP Server v3.0.
|
|
494
|
+
logger.info("π MYX Trading MCP Server v3.0.10 running (stdio, pure on-chain, prod ready)");
|
|
495
495
|
}
|
|
496
496
|
main().catch((err) => {
|
|
497
497
|
logger.error("Fatal Server Startup Error", err);
|
|
@@ -3,6 +3,74 @@ import { getChainId, resolveClient } from "../auth/resolveClient.js";
|
|
|
3
3
|
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
4
4
|
import { ensureUnits } from "../utils/units.js";
|
|
5
5
|
import { normalizeAddress } from "../utils/address.js";
|
|
6
|
+
import { Contract, parseUnits, MaxUint256 } from "ethers";
|
|
7
|
+
import { logger } from "../utils/logger.js";
|
|
8
|
+
const LP_DECIMALS = 18;
|
|
9
|
+
const POOL_MANAGER_BY_CHAIN = {
|
|
10
|
+
421614: "0xf268D9FeD3Bd56fd9aBdb4FeEb993338613678A8",
|
|
11
|
+
};
|
|
12
|
+
const POOL_MANAGER_ABI = [
|
|
13
|
+
"function deployPool((bytes32 marketId,address baseToken))",
|
|
14
|
+
"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))",
|
|
15
|
+
];
|
|
16
|
+
const LP_ROUTER_BY_CHAIN = {
|
|
17
|
+
421614: {
|
|
18
|
+
router: "0x5E16C0699de2Ee5D69a620AB38AE2c81A29a58df",
|
|
19
|
+
basePool: "0xDb41D850e9b30fc0417F488D1aB64fdc1d0DdFdc",
|
|
20
|
+
quotePool: "0xF0784c1023C051Bf1D5b8470A896c536039B06B9",
|
|
21
|
+
},
|
|
22
|
+
59141: {
|
|
23
|
+
router: "0xf1395643D77DD839E4b8d3Bb122fd0275Cf07631",
|
|
24
|
+
basePool: "0x31b093BBADbb2A3Bcd7E433E8Ed5bcdD76fFAFFe",
|
|
25
|
+
quotePool: "0xFF8A6797E2063A7a4cd1468eB7cC0db9798C753f",
|
|
26
|
+
},
|
|
27
|
+
97: {
|
|
28
|
+
router: "0xe9F2E58562aD1D50AfB1eD92EAa6D367A6D0e552",
|
|
29
|
+
basePool: "0x39506f97D1c1A91EAfD59368697E20dC1fE5eEDB",
|
|
30
|
+
quotePool: "0x80DAa474e7e2C9c86708e434B80636b92fb4e9EE",
|
|
31
|
+
},
|
|
32
|
+
56: {
|
|
33
|
+
router: "0x06d76c78B56D361de01A8903deA8aCEFFe6251d6",
|
|
34
|
+
basePool: "0x59E365A627C5A1CE459e8e3489C97Ab2BEd56FEa",
|
|
35
|
+
quotePool: "0xB1E6df749A602892FafB27bb39Fd4F044527121E",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const LP_ROUTER_BY_CHAIN_BETA = {
|
|
39
|
+
421614: {
|
|
40
|
+
router: "0xfb790ECE13Cd9e296b4a06ABF38D10431360c236",
|
|
41
|
+
basePool: "0x1F767BEa83EDe5E0C18904f439C579f52c2c0F1b",
|
|
42
|
+
quotePool: "0x50ad7da312c58c6689bEF028954a366cb5b8ee13",
|
|
43
|
+
},
|
|
44
|
+
59141: {
|
|
45
|
+
router: "0x0AE31989318565620c03d35889a0B2536b8fba5C",
|
|
46
|
+
basePool: "0xc9e3826c42183207B418116A16827C1605132c51",
|
|
47
|
+
quotePool: "0x9A9934D3b22103dE822d94A22A3177d728FE0e5a",
|
|
48
|
+
},
|
|
49
|
+
97: {
|
|
50
|
+
router: "0x0F4C6f18Fb136DD1eBd6Da3C5d86a86597CF79a3",
|
|
51
|
+
basePool: "0x51B62554a76197d5DF2D5dC4D57FF54d40775938",
|
|
52
|
+
quotePool: "0x783Ed065a12e1C1D33c2a8d6408385C1843D3084",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const PREVIEW_POOL_ABI = [
|
|
56
|
+
"function previewLpAmountOut(bytes32,uint256,uint256) view returns (uint256)",
|
|
57
|
+
"function previewQuoteAmountOut(bytes32,uint256,uint256) view returns (uint256)",
|
|
58
|
+
"function previewBaseAmountOut(bytes32,uint256,uint256) view returns (uint256)",
|
|
59
|
+
];
|
|
60
|
+
const ROUTER_ABI = [
|
|
61
|
+
"function depositQuote((bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))",
|
|
62
|
+
"function depositQuote((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[])) payable",
|
|
63
|
+
"function withdrawQuote((bytes32,uint256,uint256,address))",
|
|
64
|
+
"function withdrawQuote((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address)) payable",
|
|
65
|
+
"function depositBase((bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))",
|
|
66
|
+
"function depositBase((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[])) payable",
|
|
67
|
+
"function withdrawBase((bytes32,uint256,uint256,address))",
|
|
68
|
+
"function withdrawBase((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address)) payable",
|
|
69
|
+
];
|
|
70
|
+
const ERC20_ABI = [
|
|
71
|
+
"function allowance(address,address) view returns (uint256)",
|
|
72
|
+
"function approve(address,uint256) returns (bool)",
|
|
73
|
+
];
|
|
6
74
|
function isDivideByZeroError(message) {
|
|
7
75
|
const lower = message.toLowerCase();
|
|
8
76
|
return (lower.includes("divide_by_zero") ||
|
|
@@ -20,6 +88,264 @@ function toPositiveBigint(input) {
|
|
|
20
88
|
return null;
|
|
21
89
|
}
|
|
22
90
|
}
|
|
91
|
+
function isAbiLengthMismatchError(message) {
|
|
92
|
+
const lower = message.toLowerCase();
|
|
93
|
+
return lower.includes("abi encoding params/values length mismatch");
|
|
94
|
+
}
|
|
95
|
+
function isSdkWaitNotFunctionError(message) {
|
|
96
|
+
return message.toLowerCase().includes("wait is not a function");
|
|
97
|
+
}
|
|
98
|
+
function readLastMockTxHash() {
|
|
99
|
+
const hash = String(globalThis?.__MCP_LAST_TX_HASH ?? "").trim();
|
|
100
|
+
if (!hash.startsWith("0x") || hash.length !== 66)
|
|
101
|
+
return null;
|
|
102
|
+
return hash;
|
|
103
|
+
}
|
|
104
|
+
function recoverSdkSubmittedTxHash(beforeHash, message, context) {
|
|
105
|
+
if (!isSdkWaitNotFunctionError(message))
|
|
106
|
+
return null;
|
|
107
|
+
const recovered = readLastMockTxHash();
|
|
108
|
+
if (!recovered || recovered === beforeHash)
|
|
109
|
+
return null;
|
|
110
|
+
logger.warn(`Recovered SDK-submitted tx hash for ${context.poolType} ${context.action} after wait() incompatibility: ${recovered}`);
|
|
111
|
+
return {
|
|
112
|
+
transactionHash: recovered,
|
|
113
|
+
fallback: {
|
|
114
|
+
mode: "sdk_submitted_txhash_recovery",
|
|
115
|
+
reason: "sdk_wait_not_function",
|
|
116
|
+
poolType: context.poolType,
|
|
117
|
+
action: context.action,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function withMutedSdkAbiMismatchLogs(runner) {
|
|
122
|
+
const original = console.error;
|
|
123
|
+
console.error = (...args) => {
|
|
124
|
+
const first = args?.[0];
|
|
125
|
+
const firstText = typeof first === "string"
|
|
126
|
+
? first
|
|
127
|
+
: first instanceof Error
|
|
128
|
+
? first.message
|
|
129
|
+
: (first && typeof first === "object"
|
|
130
|
+
? JSON.stringify(first)
|
|
131
|
+
: String(first ?? ""));
|
|
132
|
+
const lower = firstText.toLowerCase();
|
|
133
|
+
if (isAbiLengthMismatchError(firstText) || lower.includes("abiencodinglengthmismatcherror")) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
original(...args);
|
|
137
|
+
};
|
|
138
|
+
try {
|
|
139
|
+
return await runner();
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
console.error = original;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function isBetaModeEnabled() {
|
|
146
|
+
return String(process.env.IS_BETA_MODE ?? "").trim().toLowerCase() === "true";
|
|
147
|
+
}
|
|
148
|
+
function getPoolManagerAddress(chainId) {
|
|
149
|
+
const envAddress = String(process.env.POOL_MANAGER_ADDRESS ?? "").trim();
|
|
150
|
+
if (envAddress) {
|
|
151
|
+
return normalizeAddress(envAddress, "POOL_MANAGER_ADDRESS");
|
|
152
|
+
}
|
|
153
|
+
const mapped = POOL_MANAGER_BY_CHAIN[chainId];
|
|
154
|
+
if (!mapped) {
|
|
155
|
+
throw new Error(`Pool manager address is not configured for chainId=${chainId}. Set POOL_MANAGER_ADDRESS env var.`);
|
|
156
|
+
}
|
|
157
|
+
return mapped;
|
|
158
|
+
}
|
|
159
|
+
function getLpAddresses(chainId) {
|
|
160
|
+
const source = isBetaModeEnabled() ? LP_ROUTER_BY_CHAIN_BETA : LP_ROUTER_BY_CHAIN;
|
|
161
|
+
const matched = source[chainId];
|
|
162
|
+
if (!matched) {
|
|
163
|
+
throw new Error(`Liquidity router config not found for chainId=${chainId}.`);
|
|
164
|
+
}
|
|
165
|
+
return matched;
|
|
166
|
+
}
|
|
167
|
+
function normalizeSlippageRatio(slippage) {
|
|
168
|
+
if (!Number.isFinite(slippage) || slippage < 0)
|
|
169
|
+
return 0;
|
|
170
|
+
if (slippage > 1 && slippage <= 100)
|
|
171
|
+
return slippage / 100;
|
|
172
|
+
if (slippage > 1)
|
|
173
|
+
return 1;
|
|
174
|
+
return slippage;
|
|
175
|
+
}
|
|
176
|
+
function applyMinOutBySlippage(amountOut, slippage) {
|
|
177
|
+
if (amountOut <= 0n)
|
|
178
|
+
return 0n;
|
|
179
|
+
const ratio = normalizeSlippageRatio(slippage);
|
|
180
|
+
const scale = 1000000n;
|
|
181
|
+
const ratioScaled = BigInt(Math.round(ratio * 1_000_000));
|
|
182
|
+
const effective = ratioScaled >= scale ? 0n : (scale - ratioScaled);
|
|
183
|
+
return (amountOut * effective) / scale;
|
|
184
|
+
}
|
|
185
|
+
function shouldUseOraclePriceByState(state) {
|
|
186
|
+
const numeric = Number(state);
|
|
187
|
+
if (!Number.isFinite(numeric))
|
|
188
|
+
return true;
|
|
189
|
+
return numeric !== 0 && numeric !== 1;
|
|
190
|
+
}
|
|
191
|
+
async function getMarketDetailOrThrow(client, chainId, poolId) {
|
|
192
|
+
const raw = await client.markets.getMarketDetail({ chainId, poolId });
|
|
193
|
+
const detail = raw?.data ?? raw;
|
|
194
|
+
if (!detail || !detail.poolId) {
|
|
195
|
+
throw new Error(`Failed to fetch market detail for poolId=${poolId}.`);
|
|
196
|
+
}
|
|
197
|
+
return detail;
|
|
198
|
+
}
|
|
199
|
+
async function buildOraclePricePayload(client, chainId, poolId, fallbackOracleType) {
|
|
200
|
+
const oracle = await client.utils.getOraclePrice(poolId, chainId);
|
|
201
|
+
const vaa = String(oracle?.vaa ?? "").trim();
|
|
202
|
+
if (!vaa || !vaa.startsWith("0x")) {
|
|
203
|
+
throw new Error(`Oracle VAA unavailable for pool ${poolId}.`);
|
|
204
|
+
}
|
|
205
|
+
const publishTime = toPositiveBigint(oracle?.publishTime) ?? BigInt(Math.floor(Date.now() / 1000));
|
|
206
|
+
const oracleType = Number.isFinite(Number(oracle?.oracleType)) ? Number(oracle.oracleType) : fallbackOracleType;
|
|
207
|
+
const value = toPositiveBigint(oracle?.value) ?? 0n;
|
|
208
|
+
const referencePrice30 = BigInt(ensureUnits(String(oracle?.price ?? "0"), 30, "oracle price"));
|
|
209
|
+
return {
|
|
210
|
+
prices: [[poolId, oracleType, publishTime, vaa]],
|
|
211
|
+
value,
|
|
212
|
+
referencePrice30,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
async function previewAmountOutForLiquidity(signer, chainId, poolId, poolType, action, amountIn, referencePrice30) {
|
|
216
|
+
try {
|
|
217
|
+
const addresses = getLpAddresses(chainId);
|
|
218
|
+
const poolAddress = poolType === "QUOTE" ? addresses.quotePool : addresses.basePool;
|
|
219
|
+
const previewContract = new Contract(poolAddress, PREVIEW_POOL_ABI, signer);
|
|
220
|
+
if (action === "deposit") {
|
|
221
|
+
const out = await previewContract.previewLpAmountOut(poolId, amountIn, referencePrice30);
|
|
222
|
+
return toPositiveBigint(out) ?? 0n;
|
|
223
|
+
}
|
|
224
|
+
if (poolType === "QUOTE") {
|
|
225
|
+
const out = await previewContract.previewQuoteAmountOut(poolId, amountIn, referencePrice30);
|
|
226
|
+
return toPositiveBigint(out) ?? 0n;
|
|
227
|
+
}
|
|
228
|
+
const out = await previewContract.previewBaseAmountOut(poolId, amountIn, referencePrice30);
|
|
229
|
+
return toPositiveBigint(out) ?? 0n;
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
logger.warn("LP preview call failed; fallback to minAmountOut=0.", extractErrorMessage(error));
|
|
233
|
+
return 0n;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async function executeLiquidityTxViaRouter(params) {
|
|
237
|
+
const { chainId, poolId, poolType, action, amount, slippage } = params;
|
|
238
|
+
const { client, signer, address } = await resolveClient();
|
|
239
|
+
const marketDetail = await getMarketDetailOrThrow(client, chainId, poolId);
|
|
240
|
+
const addresses = getLpAddresses(chainId);
|
|
241
|
+
const decimals = action === "deposit"
|
|
242
|
+
? Number(poolType === "QUOTE" ? marketDetail.quoteDecimals : marketDetail.baseDecimals)
|
|
243
|
+
: LP_DECIMALS;
|
|
244
|
+
if (!Number.isFinite(decimals) || decimals < 0) {
|
|
245
|
+
throw new Error(`Invalid decimals while preparing ${poolType} ${action} transaction.`);
|
|
246
|
+
}
|
|
247
|
+
const amountIn = parseUnits(String(amount), decimals);
|
|
248
|
+
if (amountIn <= 0n) {
|
|
249
|
+
throw new Error(`Liquidity ${poolType.toLowerCase()} ${action} amount must be > 0.`);
|
|
250
|
+
}
|
|
251
|
+
let approvalTxHash = null;
|
|
252
|
+
if (action === "deposit") {
|
|
253
|
+
const tokenAddress = normalizeAddress(poolType === "QUOTE" ? marketDetail.quoteToken : marketDetail.baseToken, poolType === "QUOTE" ? "quoteToken" : "baseToken");
|
|
254
|
+
const tokenContract = new Contract(tokenAddress, ERC20_ABI, signer);
|
|
255
|
+
const allowance = toPositiveBigint(await tokenContract.allowance(address, addresses.router)) ?? 0n;
|
|
256
|
+
if (allowance < amountIn) {
|
|
257
|
+
logger.info(`[LP fallback] allowance insufficient for ${poolType} deposit, approving router. required=${amountIn.toString()}, current=${allowance.toString()}`);
|
|
258
|
+
const approveTx = await tokenContract.approve(addresses.router, MaxUint256);
|
|
259
|
+
approvalTxHash = String(approveTx?.hash ?? "").trim() || null;
|
|
260
|
+
const approveReceipt = await approveTx?.wait?.();
|
|
261
|
+
if (approveReceipt && approveReceipt.status !== 1) {
|
|
262
|
+
throw new Error(`Router approval transaction reverted for ${poolType} deposit.`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const needOraclePrice = shouldUseOraclePriceByState(marketDetail.state);
|
|
267
|
+
let oraclePayload = {
|
|
268
|
+
prices: [],
|
|
269
|
+
value: 0n,
|
|
270
|
+
referencePrice30: 0n,
|
|
271
|
+
};
|
|
272
|
+
if (needOraclePrice) {
|
|
273
|
+
oraclePayload = await buildOraclePricePayload(client, chainId, poolId, Number(marketDetail.oracleType ?? 1));
|
|
274
|
+
}
|
|
275
|
+
const amountOut = await previewAmountOutForLiquidity(signer, chainId, poolId, poolType, action, amountIn, oraclePayload.referencePrice30);
|
|
276
|
+
const minAmountOut = applyMinOutBySlippage(amountOut, slippage);
|
|
277
|
+
const routerContract = new Contract(addresses.router, ROUTER_ABI, signer);
|
|
278
|
+
const txOverrides = {};
|
|
279
|
+
if (oraclePayload.value > 0n) {
|
|
280
|
+
txOverrides.value = oraclePayload.value;
|
|
281
|
+
}
|
|
282
|
+
let tx;
|
|
283
|
+
if (poolType === "QUOTE" && action === "deposit") {
|
|
284
|
+
const paramsTuple = [
|
|
285
|
+
poolId,
|
|
286
|
+
amountIn,
|
|
287
|
+
minAmountOut,
|
|
288
|
+
address,
|
|
289
|
+
[],
|
|
290
|
+
];
|
|
291
|
+
if (oraclePayload.prices.length > 0) {
|
|
292
|
+
tx = await routerContract["depositQuote((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))"](oraclePayload.prices, paramsTuple, txOverrides);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
tx = await routerContract["depositQuote((bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))"](paramsTuple, txOverrides);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else if (poolType === "QUOTE" && action === "withdraw") {
|
|
299
|
+
const paramsTuple = [poolId, amountIn, minAmountOut, address];
|
|
300
|
+
if (oraclePayload.prices.length > 0) {
|
|
301
|
+
tx = await routerContract["withdrawQuote((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address))"](oraclePayload.prices, paramsTuple, txOverrides);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
tx = await routerContract["withdrawQuote((bytes32,uint256,uint256,address))"](paramsTuple, txOverrides);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else if (poolType === "BASE" && action === "deposit") {
|
|
308
|
+
const paramsTuple = [
|
|
309
|
+
poolId,
|
|
310
|
+
amountIn,
|
|
311
|
+
minAmountOut,
|
|
312
|
+
address,
|
|
313
|
+
[],
|
|
314
|
+
];
|
|
315
|
+
if (oraclePayload.prices.length > 0) {
|
|
316
|
+
tx = await routerContract["depositBase((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))"](oraclePayload.prices, paramsTuple, txOverrides);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
tx = await routerContract["depositBase((bytes32,uint256,uint256,address,(uint256,uint256,uint8,uint256)[]))"](paramsTuple, txOverrides);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
const paramsTuple = [poolId, amountIn, minAmountOut, address];
|
|
324
|
+
if (oraclePayload.prices.length > 0) {
|
|
325
|
+
tx = await routerContract["withdrawBase((bytes32,uint8,uint64,bytes)[],(bytes32,uint256,uint256,address))"](oraclePayload.prices, paramsTuple, txOverrides);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
tx = await routerContract["withdrawBase((bytes32,uint256,uint256,address))"](paramsTuple, txOverrides);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const txHash = String(tx?.hash ?? "").trim();
|
|
332
|
+
if (!txHash || !txHash.startsWith("0x")) {
|
|
333
|
+
throw new Error(`${poolType} ${action} fallback sent transaction but no hash was returned.`);
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
transactionHash: txHash,
|
|
337
|
+
fallback: {
|
|
338
|
+
mode: "router_explicit_signature",
|
|
339
|
+
action,
|
|
340
|
+
poolType,
|
|
341
|
+
amountIn: amountIn.toString(),
|
|
342
|
+
minAmountOut: minAmountOut.toString(),
|
|
343
|
+
usedOraclePrice: oraclePayload.prices.length > 0,
|
|
344
|
+
oracleValue: oraclePayload.value.toString(),
|
|
345
|
+
approvalTxHash,
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
}
|
|
23
349
|
async function resolvePositiveMarketPrice30(client, poolId, chainId) {
|
|
24
350
|
if (!client)
|
|
25
351
|
return null;
|
|
@@ -52,9 +378,55 @@ async function resolvePositiveMarketPrice30(client, poolId, chainId) {
|
|
|
52
378
|
* εε»ΊεηΊ¦εΈεΊζ± ε
|
|
53
379
|
*/
|
|
54
380
|
export async function createPool(baseToken, marketId) {
|
|
55
|
-
await resolveClient();
|
|
381
|
+
const { signer } = await resolveClient();
|
|
56
382
|
const chainId = getChainId();
|
|
57
|
-
|
|
383
|
+
const normalizedBaseToken = normalizeAddress(baseToken, "baseToken");
|
|
384
|
+
const normalizedMarketId = String(marketId ?? "").trim();
|
|
385
|
+
try {
|
|
386
|
+
return await pool.createPool({ chainId, baseToken: normalizedBaseToken, marketId: normalizedMarketId });
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
const message = extractErrorMessage(error).toLowerCase();
|
|
390
|
+
if (!message.includes("deploypool is not a function")) {
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
logger.warn("SDK pool.createPool write path unavailable; falling back to direct PoolManager.deployPool.");
|
|
394
|
+
const poolManagerAddress = getPoolManagerAddress(chainId);
|
|
395
|
+
const contract = new Contract(poolManagerAddress, POOL_MANAGER_ABI, signer);
|
|
396
|
+
const tx = await contract.deployPool([normalizedMarketId, normalizedBaseToken]);
|
|
397
|
+
const txHash = String(tx?.hash ?? "").trim();
|
|
398
|
+
if (!txHash.startsWith("0x")) {
|
|
399
|
+
throw new Error("Direct deployPool fallback submitted transaction but no hash was returned.");
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
transactionHash: txHash,
|
|
403
|
+
fallback: {
|
|
404
|
+
mode: "pool_manager_direct_deploy",
|
|
405
|
+
chainId,
|
|
406
|
+
poolManagerAddress,
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
export async function getMarketPoolByBaseToken(marketId, baseToken, chainIdOverride) {
|
|
412
|
+
const { signer } = await resolveClient();
|
|
413
|
+
const chainId = chainIdOverride ?? getChainId();
|
|
414
|
+
const normalizedBaseToken = normalizeAddress(baseToken, "baseToken");
|
|
415
|
+
const normalizedMarketId = String(marketId ?? "").trim();
|
|
416
|
+
const poolManagerAddress = getPoolManagerAddress(chainId);
|
|
417
|
+
const contract = new Contract(poolManagerAddress, POOL_MANAGER_ABI, signer);
|
|
418
|
+
const record = await contract.getMarketPool(normalizedMarketId, normalizedBaseToken);
|
|
419
|
+
return {
|
|
420
|
+
poolManagerAddress,
|
|
421
|
+
chainId,
|
|
422
|
+
marketId: String(record?.marketId ?? normalizedMarketId),
|
|
423
|
+
poolId: String(record?.poolId ?? ""),
|
|
424
|
+
baseToken: String(record?.baseToken ?? normalizedBaseToken),
|
|
425
|
+
quoteToken: String(record?.quoteToken ?? ""),
|
|
426
|
+
state: Number(record?.state ?? -1),
|
|
427
|
+
basePoolToken: String(record?.basePoolToken ?? ""),
|
|
428
|
+
quotePoolToken: String(record?.quotePoolToken ?? ""),
|
|
429
|
+
};
|
|
58
430
|
}
|
|
59
431
|
/**
|
|
60
432
|
* θ·εζ± εδΏ‘ζ―
|
|
@@ -109,28 +481,80 @@ export async function getLiquidityInfo(client, poolId, marketPrice, chainIdOverr
|
|
|
109
481
|
*/
|
|
110
482
|
export async function quoteDeposit(poolId, amount, slippage, chainIdOverride) {
|
|
111
483
|
const chainId = chainIdOverride ?? getChainId();
|
|
112
|
-
|
|
484
|
+
const txHashBefore = readLastMockTxHash();
|
|
485
|
+
try {
|
|
486
|
+
return await withMutedSdkAbiMismatchLogs(() => quote.deposit({ chainId, poolId, amount, slippage }));
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
const message = extractErrorMessage(error);
|
|
490
|
+
const recovered = recoverSdkSubmittedTxHash(txHashBefore, message, { poolType: "QUOTE", action: "deposit" });
|
|
491
|
+
if (recovered)
|
|
492
|
+
return recovered;
|
|
493
|
+
if (!isAbiLengthMismatchError(message))
|
|
494
|
+
throw error;
|
|
495
|
+
logger.warn("quote.deposit hit SDK ABI mismatch; switching to explicit router path.");
|
|
496
|
+
return executeLiquidityTxViaRouter({ chainId, poolId, poolType: "QUOTE", action: "deposit", amount, slippage });
|
|
497
|
+
}
|
|
113
498
|
}
|
|
114
499
|
/**
|
|
115
500
|
* Quote ζ± withdraw
|
|
116
501
|
*/
|
|
117
502
|
export async function quoteWithdraw(poolId, amount, slippage, chainIdOverride) {
|
|
118
503
|
const chainId = chainIdOverride ?? getChainId();
|
|
119
|
-
|
|
504
|
+
const txHashBefore = readLastMockTxHash();
|
|
505
|
+
try {
|
|
506
|
+
return await withMutedSdkAbiMismatchLogs(() => quote.withdraw({ chainId, poolId, amount, slippage }));
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
const message = extractErrorMessage(error);
|
|
510
|
+
const recovered = recoverSdkSubmittedTxHash(txHashBefore, message, { poolType: "QUOTE", action: "withdraw" });
|
|
511
|
+
if (recovered)
|
|
512
|
+
return recovered;
|
|
513
|
+
if (!isAbiLengthMismatchError(message))
|
|
514
|
+
throw error;
|
|
515
|
+
logger.warn("quote.withdraw hit SDK ABI mismatch; switching to explicit router path.");
|
|
516
|
+
return executeLiquidityTxViaRouter({ chainId, poolId, poolType: "QUOTE", action: "withdraw", amount, slippage });
|
|
517
|
+
}
|
|
120
518
|
}
|
|
121
519
|
/**
|
|
122
520
|
* Base ζ± deposit
|
|
123
521
|
*/
|
|
124
522
|
export async function baseDeposit(poolId, amount, slippage, chainIdOverride) {
|
|
125
523
|
const chainId = chainIdOverride ?? getChainId();
|
|
126
|
-
|
|
524
|
+
const txHashBefore = readLastMockTxHash();
|
|
525
|
+
try {
|
|
526
|
+
return await withMutedSdkAbiMismatchLogs(() => base.deposit({ chainId, poolId, amount, slippage }));
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
const message = extractErrorMessage(error);
|
|
530
|
+
const recovered = recoverSdkSubmittedTxHash(txHashBefore, message, { poolType: "BASE", action: "deposit" });
|
|
531
|
+
if (recovered)
|
|
532
|
+
return recovered;
|
|
533
|
+
if (!isAbiLengthMismatchError(message))
|
|
534
|
+
throw error;
|
|
535
|
+
logger.warn("base.deposit hit SDK ABI mismatch; switching to explicit router path.");
|
|
536
|
+
return executeLiquidityTxViaRouter({ chainId, poolId, poolType: "BASE", action: "deposit", amount, slippage });
|
|
537
|
+
}
|
|
127
538
|
}
|
|
128
539
|
/**
|
|
129
540
|
* Base ζ± withdraw
|
|
130
541
|
*/
|
|
131
542
|
export async function baseWithdraw(poolId, amount, slippage, chainIdOverride) {
|
|
132
543
|
const chainId = chainIdOverride ?? getChainId();
|
|
133
|
-
|
|
544
|
+
const txHashBefore = readLastMockTxHash();
|
|
545
|
+
try {
|
|
546
|
+
return await withMutedSdkAbiMismatchLogs(() => base.withdraw({ chainId, poolId, amount, slippage }));
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
const message = extractErrorMessage(error);
|
|
550
|
+
const recovered = recoverSdkSubmittedTxHash(txHashBefore, message, { poolType: "BASE", action: "withdraw" });
|
|
551
|
+
if (recovered)
|
|
552
|
+
return recovered;
|
|
553
|
+
if (!isAbiLengthMismatchError(message))
|
|
554
|
+
throw error;
|
|
555
|
+
logger.warn("base.withdraw hit SDK ABI mismatch; switching to explicit router path.");
|
|
556
|
+
return executeLiquidityTxViaRouter({ chainId, poolId, poolType: "BASE", action: "withdraw", amount, slippage });
|
|
557
|
+
}
|
|
134
558
|
}
|
|
135
559
|
/**
|
|
136
560
|
* θ·ε LP δ»·ζ Ό
|
|
@@ -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
|
};
|
|
@@ -1,5 +1,28 @@
|
|
|
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 isNonEmptyObject(value) {
|
|
6
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0;
|
|
7
|
+
}
|
|
8
|
+
function buildReadErrorPayload(args, messageLike, code = "SDK_READ_ERROR") {
|
|
9
|
+
const chainId = args.chainId ?? getChainId();
|
|
10
|
+
const message = extractErrorMessage(messageLike, "Failed to read base token detail.");
|
|
11
|
+
return {
|
|
12
|
+
status: "error",
|
|
13
|
+
error: {
|
|
14
|
+
tool: "get_base_detail",
|
|
15
|
+
code,
|
|
16
|
+
message,
|
|
17
|
+
hint: "Check baseAddress validity and market availability on current chain.",
|
|
18
|
+
action: "Use list_pools/find_pool/get_pool_metadata to confirm base token, then retry.",
|
|
19
|
+
details: {
|
|
20
|
+
chainId,
|
|
21
|
+
baseAddress: args.baseAddress ?? null,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
3
26
|
export const getBaseDetailTool = {
|
|
4
27
|
name: "get_base_detail",
|
|
5
28
|
description: "[MARKET] Get base token details.",
|
|
@@ -11,11 +34,28 @@ export const getBaseDetailTool = {
|
|
|
11
34
|
try {
|
|
12
35
|
const { client } = await resolveClient();
|
|
13
36
|
const chainId = args.chainId ?? getChainId();
|
|
14
|
-
const
|
|
15
|
-
|
|
37
|
+
const baseAddress = normalizeAddress(args.baseAddress, "baseAddress");
|
|
38
|
+
const result = await client.markets.getBaseDetail({ chainId, baseAddress });
|
|
39
|
+
const hasCode = !!result && typeof result === "object" && !Array.isArray(result) && Object.prototype.hasOwnProperty.call(result, "code");
|
|
40
|
+
const code = hasCode ? Number(result.code) : 0;
|
|
41
|
+
const payload = hasCode ? result.data : result;
|
|
42
|
+
if (hasCode && Number.isFinite(code) && code !== 0) {
|
|
43
|
+
const body = buildReadErrorPayload({ ...args, baseAddress }, result.msg ?? result.message ?? result, "SDK_READ_ERROR");
|
|
44
|
+
return { content: [{ type: "text", text: JSON.stringify(body, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }], isError: true };
|
|
45
|
+
}
|
|
46
|
+
if (payload === null || payload === undefined) {
|
|
47
|
+
const body = buildReadErrorPayload({ ...args, baseAddress }, "get_base_detail returned empty data.", "NOT_FOUND");
|
|
48
|
+
return { content: [{ type: "text", text: JSON.stringify(body, null, 2) }], isError: true };
|
|
49
|
+
}
|
|
50
|
+
if (typeof payload === "object" && !Array.isArray(payload) && !isNonEmptyObject(payload)) {
|
|
51
|
+
const body = buildReadErrorPayload({ ...args, baseAddress }, "get_base_detail returned an empty object.", "NOT_FOUND");
|
|
52
|
+
return { content: [{ type: "text", text: JSON.stringify(body, null, 2) }], isError: true };
|
|
53
|
+
}
|
|
54
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: payload }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
|
|
16
55
|
}
|
|
17
56
|
catch (error) {
|
|
18
|
-
|
|
57
|
+
const body = buildReadErrorPayload(args, error, "TOOL_EXECUTION_ERROR");
|
|
58
|
+
return { content: [{ type: "text", text: JSON.stringify(body, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }], isError: true };
|
|
19
59
|
}
|
|
20
60
|
},
|
|
21
61
|
};
|
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
|
}));
|
package/dist/tools/manageTpSl.js
CHANGED
|
@@ -39,6 +39,66 @@ function normalizeUnitsInput(text) {
|
|
|
39
39
|
return `raw:${raw}`;
|
|
40
40
|
return raw;
|
|
41
41
|
}
|
|
42
|
+
function isExplicitZeroValue(value) {
|
|
43
|
+
if (value === undefined || value === null)
|
|
44
|
+
return false;
|
|
45
|
+
if (typeof value === "number") {
|
|
46
|
+
return Number.isFinite(value) && value === 0;
|
|
47
|
+
}
|
|
48
|
+
const text = String(value).trim();
|
|
49
|
+
if (!text)
|
|
50
|
+
return false;
|
|
51
|
+
const payload = /^(raw|human):/i.test(text) ? text.replace(/^(raw|human):/i, "").trim() : text;
|
|
52
|
+
if (!payload)
|
|
53
|
+
return false;
|
|
54
|
+
if (!/^[-+]?\d+(\.\d+)?$/.test(payload))
|
|
55
|
+
return false;
|
|
56
|
+
return Number(payload) === 0;
|
|
57
|
+
}
|
|
58
|
+
function normalizeId(value) {
|
|
59
|
+
return String(value ?? "").trim().toLowerCase();
|
|
60
|
+
}
|
|
61
|
+
function hasOwnKey(input, key) {
|
|
62
|
+
return !!input && typeof input === "object" && Object.prototype.hasOwnProperty.call(input, key);
|
|
63
|
+
}
|
|
64
|
+
function isDeleteTpSlIntent(args) {
|
|
65
|
+
if (!hasOwnKey(args, "tpPrice") || !hasOwnKey(args, "slPrice"))
|
|
66
|
+
return false;
|
|
67
|
+
return isExplicitZeroValue(args.tpPrice) && isExplicitZeroValue(args.slPrice);
|
|
68
|
+
}
|
|
69
|
+
function isInvalidParameterRevert(error) {
|
|
70
|
+
const message = String(error?.message ?? error ?? "").toLowerCase();
|
|
71
|
+
return (message.includes("0x613970e0") ||
|
|
72
|
+
message.includes("invalidparameter") ||
|
|
73
|
+
message.includes("invalid parameter"));
|
|
74
|
+
}
|
|
75
|
+
function readOrderId(order) {
|
|
76
|
+
const id = String(order?.orderId ?? order?.id ?? "").trim();
|
|
77
|
+
return id;
|
|
78
|
+
}
|
|
79
|
+
function isTpSlOrder(order) {
|
|
80
|
+
const orderType = Number(order?.orderType ?? order?.type);
|
|
81
|
+
const operation = Number(order?.operation ?? order?.op);
|
|
82
|
+
if (Number.isFinite(orderType) && Number.isFinite(operation)) {
|
|
83
|
+
return orderType === 2 && operation === 1;
|
|
84
|
+
}
|
|
85
|
+
// Fallback: when operation/orderType are missing, try TP/SL related shape.
|
|
86
|
+
return isNonEmpty(order?.triggerType) && isNonEmpty(order?.positionId);
|
|
87
|
+
}
|
|
88
|
+
async function findOpenTpSlOrderIdsForPosition(client, address, poolId, positionId) {
|
|
89
|
+
const targetPool = normalizeId(poolId);
|
|
90
|
+
const targetPosition = normalizeId(positionId);
|
|
91
|
+
if (!targetPool || !targetPosition)
|
|
92
|
+
return [];
|
|
93
|
+
const openRes = await client.order.getOrders(address);
|
|
94
|
+
const openOrders = Array.isArray(openRes?.data) ? openRes.data : [];
|
|
95
|
+
return openOrders
|
|
96
|
+
.filter((order) => normalizeId(order?.poolId ?? order?.pool_id) === targetPool)
|
|
97
|
+
.filter((order) => normalizeId(order?.positionId ?? order?.position_id) === targetPosition)
|
|
98
|
+
.filter((order) => isTpSlOrder(order))
|
|
99
|
+
.map((order) => readOrderId(order))
|
|
100
|
+
.filter((id) => id.length > 0);
|
|
101
|
+
}
|
|
42
102
|
async function findOrderSnapshot(client, address, chainId, orderId, poolId) {
|
|
43
103
|
const target = String(orderId).toLowerCase();
|
|
44
104
|
try {
|
|
@@ -100,6 +160,63 @@ function resolvePositionSizeRaw(positionSnapshot, baseDecimals) {
|
|
|
100
160
|
}
|
|
101
161
|
return "";
|
|
102
162
|
}
|
|
163
|
+
async function cancelTpSlByIntent(client, address, signer, chainId, args) {
|
|
164
|
+
if (args.orderId) {
|
|
165
|
+
const raw = await client.order.cancelAllOrders([String(args.orderId)], chainId);
|
|
166
|
+
const data = await finalizeMutationResult(raw, signer, "manage_tp_sl_delete");
|
|
167
|
+
return {
|
|
168
|
+
content: [{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: JSON.stringify({
|
|
171
|
+
status: "success",
|
|
172
|
+
data: {
|
|
173
|
+
mode: "delete_tpsl_by_order",
|
|
174
|
+
cancelledCount: 1,
|
|
175
|
+
orderIds: [String(args.orderId)],
|
|
176
|
+
result: data
|
|
177
|
+
}
|
|
178
|
+
}, (_, v) => typeof v === "bigint" ? v.toString() : v, 2)
|
|
179
|
+
}]
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (!args.positionId) {
|
|
183
|
+
throw new Error("positionId (or orderId) is required when deleting TP/SL with tpPrice=0 and slPrice=0.");
|
|
184
|
+
}
|
|
185
|
+
const orderIds = await findOpenTpSlOrderIdsForPosition(client, address, args.poolId, args.positionId);
|
|
186
|
+
if (orderIds.length === 0) {
|
|
187
|
+
return {
|
|
188
|
+
content: [{
|
|
189
|
+
type: "text",
|
|
190
|
+
text: JSON.stringify({
|
|
191
|
+
status: "success",
|
|
192
|
+
data: {
|
|
193
|
+
mode: "delete_tpsl_by_position",
|
|
194
|
+
cancelledCount: 0,
|
|
195
|
+
positionId: args.positionId,
|
|
196
|
+
message: "No open TP/SL orders found for this position."
|
|
197
|
+
}
|
|
198
|
+
}, null, 2)
|
|
199
|
+
}]
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const raw = await client.order.cancelAllOrders(orderIds, chainId);
|
|
203
|
+
const data = await finalizeMutationResult(raw, signer, "manage_tp_sl_delete");
|
|
204
|
+
return {
|
|
205
|
+
content: [{
|
|
206
|
+
type: "text",
|
|
207
|
+
text: JSON.stringify({
|
|
208
|
+
status: "success",
|
|
209
|
+
data: {
|
|
210
|
+
mode: "delete_tpsl_by_position",
|
|
211
|
+
cancelledCount: orderIds.length,
|
|
212
|
+
positionId: args.positionId,
|
|
213
|
+
orderIds,
|
|
214
|
+
result: data
|
|
215
|
+
}
|
|
216
|
+
}, (_, v) => typeof v === "bigint" ? v.toString() : v, 2)
|
|
217
|
+
}]
|
|
218
|
+
};
|
|
219
|
+
}
|
|
103
220
|
export const manageTpSlTool = {
|
|
104
221
|
name: "manage_tp_sl",
|
|
105
222
|
description: "[TRADE] Set or update Take Profit (TP) and Stop Loss (SL) for a position.",
|
|
@@ -126,6 +243,10 @@ export const manageTpSlTool = {
|
|
|
126
243
|
const chainId = args.chainId ?? getChainId();
|
|
127
244
|
const direction = normalizeDirectionInput(args.direction);
|
|
128
245
|
const slippagePct = args.slippagePct ?? "100";
|
|
246
|
+
const wantsDeleteTpSl = isDeleteTpSlIntent(args);
|
|
247
|
+
if (wantsDeleteTpSl) {
|
|
248
|
+
return await cancelTpSlByIntent(client, address, signer, chainId, args);
|
|
249
|
+
}
|
|
129
250
|
const { setPositionTpSl, updateOrderTpSl } = await import("../services/tradeService.js");
|
|
130
251
|
const { getMarketDetail } = await import("../services/marketService.js");
|
|
131
252
|
const marketRes = await getMarketDetail(client, args.poolId, chainId);
|
|
@@ -162,19 +283,27 @@ export const manageTpSlTool = {
|
|
|
162
283
|
if ((slPrice && !slSize) || (!slPrice && slSize)) {
|
|
163
284
|
throw new Error("SL update requires both slPrice and slSize (or resolvable existing SL fields).");
|
|
164
285
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
286
|
+
try {
|
|
287
|
+
raw = await updateOrderTpSl(client, address, {
|
|
288
|
+
orderId: args.orderId,
|
|
289
|
+
marketId: market.marketId,
|
|
290
|
+
poolId: args.poolId,
|
|
291
|
+
size: normalizeUnitsInput(size),
|
|
292
|
+
price: normalizeUnitsInput(price),
|
|
293
|
+
tpPrice: tpPrice ? normalizeUnitsInput(tpPrice) : "0",
|
|
294
|
+
tpSize: tpSize ? normalizeUnitsInput(tpSize) : "0",
|
|
295
|
+
slPrice: slPrice ? normalizeUnitsInput(slPrice) : "0",
|
|
296
|
+
slSize: slSize ? normalizeUnitsInput(slSize) : "0",
|
|
297
|
+
quoteToken: market.quoteToken,
|
|
298
|
+
useOrderCollateral: args.useOrderCollateral ?? true
|
|
299
|
+
}, chainId);
|
|
300
|
+
}
|
|
301
|
+
catch (updateError) {
|
|
302
|
+
if (isDeleteTpSlIntent(args) || (isInvalidParameterRevert(updateError) && isExplicitZeroValue(args.tpPrice) && isExplicitZeroValue(args.slPrice))) {
|
|
303
|
+
return await cancelTpSlByIntent(client, address, signer, chainId, args);
|
|
304
|
+
}
|
|
305
|
+
throw updateError;
|
|
306
|
+
}
|
|
178
307
|
}
|
|
179
308
|
else {
|
|
180
309
|
// Create new
|
|
@@ -211,18 +340,26 @@ export const manageTpSlTool = {
|
|
|
211
340
|
if (needsSlSize)
|
|
212
341
|
slSizeInput = `raw:${positionSizeRaw}`;
|
|
213
342
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
343
|
+
try {
|
|
344
|
+
raw = await setPositionTpSl(client, address, {
|
|
345
|
+
poolId: args.poolId,
|
|
346
|
+
positionId: args.positionId,
|
|
347
|
+
direction: resolvedDirection,
|
|
348
|
+
leverage: Number(resolvedLeverage),
|
|
349
|
+
executionFeeToken: args.executionFeeToken || market.quoteToken,
|
|
350
|
+
slippagePct,
|
|
351
|
+
tpPrice: args.tpPrice,
|
|
352
|
+
tpSize: tpSizeInput || undefined,
|
|
353
|
+
slPrice: args.slPrice,
|
|
354
|
+
slSize: slSizeInput || undefined
|
|
355
|
+
}, chainId);
|
|
356
|
+
}
|
|
357
|
+
catch (setError) {
|
|
358
|
+
if (isDeleteTpSlIntent(args) || (isInvalidParameterRevert(setError) && isExplicitZeroValue(args.tpPrice) && isExplicitZeroValue(args.slPrice))) {
|
|
359
|
+
return await cancelTpSlByIntent(client, address, signer, chainId, args);
|
|
360
|
+
}
|
|
361
|
+
throw setError;
|
|
362
|
+
}
|
|
226
363
|
}
|
|
227
364
|
const data = await finalizeMutationResult(raw, signer, "manage_tp_sl");
|
|
228
365
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
@@ -28,6 +28,7 @@ export function injectBrowserProviderMock(chainId, provider, signer) {
|
|
|
28
28
|
tx.from = signer.address;
|
|
29
29
|
const txRes = await signer.sendTransaction(tx);
|
|
30
30
|
console.error(`[MCP Router] δΊ€ζε·²ζδΊ€οΌεεΈ: ${txRes.hash}`);
|
|
31
|
+
g.__MCP_LAST_TX_HASH = txRes.hash;
|
|
31
32
|
return txRes.hash;
|
|
32
33
|
}
|
|
33
34
|
if (method === "eth_estimateGas" || method === "eth_gasPrice" || method === "eth_getBalance" || method === "eth_call") {
|
package/dist/utils/mappings.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Direction: LONG=0, SHORT=1
|
|
3
3
|
*/
|
|
4
|
+
function formatUnknown(value) {
|
|
5
|
+
if (value === undefined || value === null || value === "")
|
|
6
|
+
return "Unknown";
|
|
7
|
+
return `Unknown(${String(value)})`;
|
|
8
|
+
}
|
|
4
9
|
export const getDirectionDesc = (direction) => {
|
|
5
10
|
switch (direction) {
|
|
6
11
|
case 0: return "Long";
|
|
7
12
|
case 1: return "Short";
|
|
8
|
-
default: return
|
|
13
|
+
default: return formatUnknown(direction);
|
|
9
14
|
}
|
|
10
15
|
};
|
|
11
16
|
/**
|
|
@@ -17,7 +22,7 @@ export const getOrderTypeDesc = (type) => {
|
|
|
17
22
|
case 1: return "Limit";
|
|
18
23
|
case 2: return "Stop";
|
|
19
24
|
case 3: return "Conditional";
|
|
20
|
-
default: return
|
|
25
|
+
default: return formatUnknown(type);
|
|
21
26
|
}
|
|
22
27
|
};
|
|
23
28
|
/**
|
|
@@ -31,7 +36,7 @@ export const getOrderStatusDesc = (status) => {
|
|
|
31
36
|
case 3: return "Cancelled";
|
|
32
37
|
case 4: return "Rejected";
|
|
33
38
|
case 5: return "Expired";
|
|
34
|
-
default: return
|
|
39
|
+
default: return formatUnknown(status);
|
|
35
40
|
}
|
|
36
41
|
};
|
|
37
42
|
/**
|
|
@@ -43,7 +48,7 @@ export const getHistoryOrderStatusDesc = (status) => {
|
|
|
43
48
|
case 2: return "Expired";
|
|
44
49
|
case 9: return "Successful";
|
|
45
50
|
case 8: return "PartialFilled";
|
|
46
|
-
default: return
|
|
51
|
+
default: return formatUnknown(status);
|
|
47
52
|
}
|
|
48
53
|
};
|
|
49
54
|
/**
|
|
@@ -83,7 +88,7 @@ export const getTradeFlowTypeDesc = (type) => {
|
|
|
83
88
|
13: "ReferralReward",
|
|
84
89
|
14: "ReferralRewardClaim"
|
|
85
90
|
};
|
|
86
|
-
return types[type] ||
|
|
91
|
+
return types[type] || formatUnknown(type);
|
|
87
92
|
};
|
|
88
93
|
/**
|
|
89
94
|
* ExecTypeEnum
|
|
@@ -100,7 +105,7 @@ export const getExecTypeDesc = (type) => {
|
|
|
100
105
|
8: "EarlyClose",
|
|
101
106
|
9: "MarketClose"
|
|
102
107
|
};
|
|
103
|
-
return types[type] ||
|
|
108
|
+
return types[type] || formatUnknown(type);
|
|
104
109
|
};
|
|
105
110
|
/**
|
|
106
111
|
* CloseTypeEnum
|
|
@@ -118,7 +123,7 @@ export const getCloseTypeDesc = (type) => {
|
|
|
118
123
|
8: "SL",
|
|
119
124
|
9: "Increase"
|
|
120
125
|
};
|
|
121
|
-
return types[type] ||
|
|
126
|
+
return types[type] || formatUnknown(type);
|
|
122
127
|
};
|
|
123
128
|
/**
|
|
124
129
|
* ζ ε°θΎε
₯ζΉεδΈΊζ°εΌ
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michaleffffff/mcp-trading-server",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"myx-mcp": "dist/server.js"
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"start": "node dist/server.js",
|
|
17
17
|
"dev": "tsx src/server.ts",
|
|
18
18
|
"test": "tsx tests/test_sepolia.ts",
|
|
19
|
-
"test:new-tools": "tsx tests/test_new_tools_p0_p1.ts"
|
|
19
|
+
"test:new-tools": "tsx tests/test_new_tools_p0_p1.ts",
|
|
20
|
+
"test:manage-tpsl-delete": "tsx tests/test_manage_tpsl_delete_live.ts"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"@modelcontextprotocol/sdk": "^1.27.1",
|