@michaleffffff/mcp-trading-server 2.4.3 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/tradeService.js +54 -20
- package/dist/tools/adjustMargin.js +2 -2
- package/dist/tools/closeAllPositions.js +5 -2
- package/dist/tools/closePosition.js +10 -7
- package/dist/tools/executeTrade.js +18 -15
- package/dist/tools/setTpSl.js +12 -9
- package/dist/utils/slippage.js +15 -0
- package/dist/utils/units.js +10 -2
- package/package.json +1 -1
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
import { Direction, OrderType, TriggerType } from "@myx-trade/sdk";
|
|
2
|
-
import { parseUnits } from "ethers";
|
|
3
2
|
import { getChainId, getQuoteToken } from "../auth/resolveClient.js";
|
|
4
3
|
import { ensureUnits } from "../utils/units.js";
|
|
5
4
|
import { normalizeAddress } from "../utils/address.js";
|
|
6
|
-
|
|
7
|
-
* 将人类可读数值转为指定精度的整数字串
|
|
8
|
-
* e.g. toUnits("100", 6) => "100000000"
|
|
9
|
-
*/
|
|
10
|
-
export function toUnits(human, decimals) {
|
|
11
|
-
return parseUnits(human, decimals).toString();
|
|
12
|
-
}
|
|
5
|
+
import { normalizeSlippagePct4dp } from "../utils/slippage.js";
|
|
13
6
|
function resolveDirection(direction) {
|
|
14
7
|
if (direction !== 0 && direction !== 1) {
|
|
15
8
|
throw new Error("direction must be 0 (LONG) or 1 (SHORT).");
|
|
16
9
|
}
|
|
17
10
|
return direction === 0 ? Direction.LONG : Direction.SHORT;
|
|
18
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* 自动推断开启订单的触发类型 (Limit/Stop)
|
|
14
|
+
*/
|
|
15
|
+
function resolveTriggerType(orderType, direction, triggerType) {
|
|
16
|
+
if (triggerType !== undefined && triggerType !== null && triggerType !== 0) {
|
|
17
|
+
return triggerType;
|
|
18
|
+
}
|
|
19
|
+
// LIMIT LONG: LTE(2), LIMIT SHORT: GTE(1)
|
|
20
|
+
if (orderType === OrderType.LIMIT) {
|
|
21
|
+
return direction === 0 ? 2 : 1;
|
|
22
|
+
}
|
|
23
|
+
// STOP LONG: GTE(1), STOP SHORT: LTE(2)
|
|
24
|
+
if (orderType === OrderType.STOP) {
|
|
25
|
+
return direction === 0 ? 1 : 2;
|
|
26
|
+
}
|
|
27
|
+
return 0; // MARKET order typically uses 0
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 自动推断已有仓位的止盈止损触发类型
|
|
31
|
+
*/
|
|
32
|
+
function resolveTpSlTriggerType(isTp, direction, triggerType) {
|
|
33
|
+
if (triggerType !== undefined && triggerType !== null && triggerType !== 0) {
|
|
34
|
+
return triggerType;
|
|
35
|
+
}
|
|
36
|
+
// TP: LONG -> GTE(1), SHORT -> LTE(2)
|
|
37
|
+
// SL: LONG -> LTE(2), SHORT -> GTE(1)
|
|
38
|
+
if (isTp) {
|
|
39
|
+
return direction === 0 ? 1 : 2;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return direction === 0 ? 2 : 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
19
45
|
/**
|
|
20
46
|
* 开仓 / 加仓
|
|
21
47
|
*/
|
|
@@ -38,14 +64,14 @@ export async function openPosition(client, address, args) {
|
|
|
38
64
|
poolId: args.poolId,
|
|
39
65
|
positionId: args.positionId,
|
|
40
66
|
orderType: args.orderType,
|
|
41
|
-
triggerType: args.triggerType,
|
|
67
|
+
triggerType: resolveTriggerType(args.orderType, args.direction, args.triggerType),
|
|
42
68
|
direction: dir,
|
|
43
69
|
collateralAmount: ensureUnits(args.collateralAmount, quoteDecimals, "collateralAmount"),
|
|
44
70
|
size: ensureUnits(args.size, baseDecimals, "size"),
|
|
45
71
|
price: ensureUnits(args.price, 30, "price"),
|
|
46
72
|
timeInForce: args.timeInForce,
|
|
47
73
|
postOnly: args.postOnly,
|
|
48
|
-
slippagePct:
|
|
74
|
+
slippagePct: normalizeSlippagePct4dp(args.slippagePct),
|
|
49
75
|
executionFeeToken,
|
|
50
76
|
leverage: args.leverage,
|
|
51
77
|
};
|
|
@@ -57,7 +83,8 @@ export async function openPosition(client, address, args) {
|
|
|
57
83
|
orderParams.slSize = ensureUnits(args.slSize, baseDecimals, "slSize");
|
|
58
84
|
if (args.slPrice)
|
|
59
85
|
orderParams.slPrice = ensureUnits(args.slPrice, 30, "slPrice");
|
|
60
|
-
|
|
86
|
+
const tradingFeeRaw = ensureUnits(args.tradingFee, quoteDecimals, "tradingFee");
|
|
87
|
+
return client.order.createIncreaseOrder(orderParams, tradingFeeRaw, args.marketId);
|
|
61
88
|
}
|
|
62
89
|
/**
|
|
63
90
|
* 平仓 / 减仓
|
|
@@ -84,14 +111,14 @@ export async function closePosition(client, address, args) {
|
|
|
84
111
|
poolId: args.poolId,
|
|
85
112
|
positionId: args.positionId,
|
|
86
113
|
orderType: args.orderType,
|
|
87
|
-
triggerType: args.triggerType,
|
|
114
|
+
triggerType: resolveTriggerType(args.orderType, args.direction, args.triggerType),
|
|
88
115
|
direction: dir,
|
|
89
116
|
collateralAmount: ensureUnits(args.collateralAmount, quoteDecimals, "collateralAmount"),
|
|
90
117
|
size: ensureUnits(args.size, baseDecimals, "size"),
|
|
91
118
|
price: ensureUnits(args.price, 30, "price"),
|
|
92
119
|
timeInForce: args.timeInForce,
|
|
93
120
|
postOnly: args.postOnly,
|
|
94
|
-
slippagePct:
|
|
121
|
+
slippagePct: normalizeSlippagePct4dp(args.slippagePct),
|
|
95
122
|
executionFeeToken,
|
|
96
123
|
leverage: args.leverage,
|
|
97
124
|
});
|
|
@@ -106,6 +133,13 @@ export async function setPositionTpSl(client, address, args) {
|
|
|
106
133
|
throw new Error("At least one of tpPrice or slPrice must be provided.");
|
|
107
134
|
}
|
|
108
135
|
const dir = resolveDirection(args.direction);
|
|
136
|
+
// Fetch pool detail for decimals
|
|
137
|
+
const poolResponse = await client.markets.getMarketDetail({ chainId, poolId: args.poolId });
|
|
138
|
+
const poolData = poolResponse?.data || (poolResponse?.marketId ? poolResponse : null);
|
|
139
|
+
if (!poolData) {
|
|
140
|
+
throw new Error(`Could not find pool metadata for ID: ${args.poolId}`);
|
|
141
|
+
}
|
|
142
|
+
const baseDecimals = poolData.baseDecimals || 18;
|
|
109
143
|
const params = {
|
|
110
144
|
chainId,
|
|
111
145
|
address,
|
|
@@ -114,18 +148,18 @@ export async function setPositionTpSl(client, address, args) {
|
|
|
114
148
|
direction: dir,
|
|
115
149
|
leverage: args.leverage,
|
|
116
150
|
executionFeeToken,
|
|
117
|
-
tpTriggerType: args.tpTriggerType,
|
|
118
|
-
slTriggerType: args.slTriggerType,
|
|
119
|
-
slippagePct: args.slippagePct,
|
|
151
|
+
tpTriggerType: resolveTpSlTriggerType(true, args.direction, args.tpTriggerType),
|
|
152
|
+
slTriggerType: resolveTpSlTriggerType(false, args.direction, args.slTriggerType),
|
|
153
|
+
slippagePct: normalizeSlippagePct4dp(args.slippagePct),
|
|
120
154
|
};
|
|
121
155
|
if (args.tpPrice)
|
|
122
|
-
params.tpPrice = args.tpPrice;
|
|
156
|
+
params.tpPrice = ensureUnits(args.tpPrice, 30, "tpPrice");
|
|
123
157
|
if (args.tpSize)
|
|
124
|
-
params.tpSize = args.tpSize;
|
|
158
|
+
params.tpSize = ensureUnits(args.tpSize, baseDecimals, "tpSize");
|
|
125
159
|
if (args.slPrice)
|
|
126
|
-
params.slPrice = args.slPrice;
|
|
160
|
+
params.slPrice = ensureUnits(args.slPrice, 30, "slPrice");
|
|
127
161
|
if (args.slSize)
|
|
128
|
-
params.slSize = args.slSize;
|
|
162
|
+
params.slSize = ensureUnits(args.slSize, baseDecimals, "slSize");
|
|
129
163
|
return client.order.createPositionTpSlOrder(params);
|
|
130
164
|
}
|
|
131
165
|
/**
|
|
@@ -8,9 +8,9 @@ export const adjustMarginTool = {
|
|
|
8
8
|
schema: {
|
|
9
9
|
poolId: z.string().describe("Pool ID"),
|
|
10
10
|
positionId: z.string().describe("Position ID"),
|
|
11
|
-
adjustAmount: z.string().regex(/^-?\d+$/).describe("Quote token raw units. Positive = add, negative = remove."),
|
|
11
|
+
adjustAmount: z.coerce.string().regex(/^-?\d+$/).describe("Quote token raw units. Positive = add, negative = remove."),
|
|
12
12
|
quoteToken: z.string().optional().describe("Quote token address"),
|
|
13
|
-
poolOracleType: z.number().optional().describe("Oracle type: 1 for Chainlink, 2 for Pyth"),
|
|
13
|
+
poolOracleType: z.coerce.number().optional().describe("Oracle type: 1 for Chainlink, 2 for Pyth"),
|
|
14
14
|
},
|
|
15
15
|
handler: async (args) => {
|
|
16
16
|
try {
|
|
@@ -3,12 +3,15 @@ import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.
|
|
|
3
3
|
import { getOraclePrice } from "../services/marketService.js";
|
|
4
4
|
import { ensureUnits } from "../utils/units.js";
|
|
5
5
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
6
|
+
import { normalizeSlippagePct4dp, isValidSlippagePct4dp, SLIPPAGE_PCT_4DP_DESC } from "../utils/slippage.js";
|
|
6
7
|
export const closeAllPositionsTool = {
|
|
7
8
|
name: "close_all_positions",
|
|
8
9
|
description: "Emergency: close ALL open positions in a pool at once. Use for risk management.",
|
|
9
10
|
schema: {
|
|
10
11
|
poolId: z.string().describe("Pool ID to close all positions in"),
|
|
11
|
-
slippagePct: z.string().
|
|
12
|
+
slippagePct: z.coerce.string().refine(isValidSlippagePct4dp, {
|
|
13
|
+
message: "slippagePct must be an integer in [0, 10000] with 4-decimal precision (1 = 0.01%).",
|
|
14
|
+
}).optional().describe(SLIPPAGE_PCT_4DP_DESC),
|
|
12
15
|
},
|
|
13
16
|
handler: async (args) => {
|
|
14
17
|
try {
|
|
@@ -25,7 +28,7 @@ export const closeAllPositionsTool = {
|
|
|
25
28
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: { message: "No open positions in this pool.", closed: 0 } }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
26
29
|
}
|
|
27
30
|
// 2) 为每个仓位构建平仓参数
|
|
28
|
-
const slippagePct = args.slippagePct ?? "200";
|
|
31
|
+
const slippagePct = normalizeSlippagePct4dp(args.slippagePct ?? "200");
|
|
29
32
|
// 3) We need actual oracle prices to avoid Revert 0x613970e0 (InvalidParameter)
|
|
30
33
|
const oraclePriceReq = await getOraclePrice(client, args.poolId).catch(() => null);
|
|
31
34
|
let fallbackPrice = "0";
|
|
@@ -2,23 +2,26 @@ import { z } from "zod";
|
|
|
2
2
|
import { resolveClient } from "../auth/resolveClient.js";
|
|
3
3
|
import { closePosition as closePos } from "../services/tradeService.js";
|
|
4
4
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
5
|
+
import { isValidSlippagePct4dp, SLIPPAGE_PCT_4DP_DESC } from "../utils/slippage.js";
|
|
5
6
|
export const closePositionTool = {
|
|
6
7
|
name: "close_position",
|
|
7
8
|
description: "Create a decrease order using SDK-native parameters.",
|
|
8
9
|
schema: {
|
|
9
10
|
poolId: z.string().describe("Pool ID"),
|
|
10
11
|
positionId: z.string().describe("Position ID to close"),
|
|
11
|
-
orderType: z.number().int().min(0).max(3).describe("OrderType enum value"),
|
|
12
|
-
triggerType: z.number().int().min(0).max(2).describe("TriggerType enum value"),
|
|
13
|
-
direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT"),
|
|
12
|
+
orderType: z.coerce.number().int().min(0).max(3).describe("OrderType enum value"),
|
|
13
|
+
triggerType: z.coerce.number().int().min(0).max(2).describe("TriggerType enum value"),
|
|
14
|
+
direction: z.coerce.number().pipe(z.union([z.literal(0), z.literal(1)])).describe("0 = LONG, 1 = SHORT"),
|
|
14
15
|
collateralAmount: z.union([z.string(), z.number()]).describe("Collateral amount (human-readable or raw units)"),
|
|
15
16
|
size: z.union([z.string(), z.number()]).describe("Position size (human-readable or raw units)"),
|
|
16
17
|
price: z.union([z.string(), z.number()]).describe("Price (human-readable or 30-dec raw units)"),
|
|
17
|
-
timeInForce: z.number().int().describe("TimeInForce enum value"),
|
|
18
|
-
postOnly: z.boolean().describe("Post-only flag"),
|
|
19
|
-
slippagePct: z.
|
|
18
|
+
timeInForce: z.coerce.number().int().describe("TimeInForce enum value"),
|
|
19
|
+
postOnly: z.coerce.boolean().describe("Post-only flag"),
|
|
20
|
+
slippagePct: z.coerce.string().refine(isValidSlippagePct4dp, {
|
|
21
|
+
message: "slippagePct must be an integer in [0, 10000] with 4-decimal precision (1 = 0.01%).",
|
|
22
|
+
}).describe(SLIPPAGE_PCT_4DP_DESC),
|
|
20
23
|
executionFeeToken: z.string().describe("Execution fee token address"),
|
|
21
|
-
leverage: z.number().describe("Leverage"),
|
|
24
|
+
leverage: z.coerce.number().describe("Leverage"),
|
|
22
25
|
},
|
|
23
26
|
handler: async (args) => {
|
|
24
27
|
try {
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { resolveClient } from "../auth/resolveClient.js";
|
|
3
3
|
import { openPosition } from "../services/tradeService.js";
|
|
4
4
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
5
|
+
import { isValidSlippagePct4dp, SLIPPAGE_PCT_4DP_DESC } from "../utils/slippage.js";
|
|
5
6
|
const POSITION_ID_RE = /^$|^0x[0-9a-fA-F]{64}$/;
|
|
6
7
|
function toList(input) {
|
|
7
8
|
if (Array.isArray(input))
|
|
@@ -81,22 +82,24 @@ export const executeTradeTool = {
|
|
|
81
82
|
positionId: z.string().refine((value) => POSITION_ID_RE.test(value), {
|
|
82
83
|
message: "positionId must be empty string for new position, or a bytes32 hex string.",
|
|
83
84
|
}).describe("Position ID: empty string for new position"),
|
|
84
|
-
orderType: z.number().int().min(0).max(3).describe("OrderType enum value"),
|
|
85
|
-
triggerType: z.number().int().min(0).max(2).describe("TriggerType enum value"),
|
|
86
|
-
direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT"),
|
|
87
|
-
collateralAmount: z.string().regex(/^\d+$/).describe("Collateral amount (raw units)"),
|
|
88
|
-
size: z.string().regex(/^\d+$/).describe("Position size (raw units)"),
|
|
89
|
-
price: z.string().regex(/^\d+$/).describe("Price (30-decimal raw units)"),
|
|
90
|
-
timeInForce: z.number().int().describe("TimeInForce enum value"),
|
|
91
|
-
postOnly: z.boolean().describe("Post-only flag"),
|
|
92
|
-
slippagePct: z.string().
|
|
85
|
+
orderType: z.coerce.number().int().min(0).max(3).describe("OrderType enum value"),
|
|
86
|
+
triggerType: z.coerce.number().int().min(0).max(2).describe("TriggerType enum value"),
|
|
87
|
+
direction: z.coerce.number().pipe(z.union([z.literal(0), z.literal(1)])).describe("0 = LONG, 1 = SHORT"),
|
|
88
|
+
collateralAmount: z.coerce.string().regex(/^\d+$/).describe("Collateral amount (raw units)"),
|
|
89
|
+
size: z.coerce.string().regex(/^\d+$/).describe("Position size (raw units)"),
|
|
90
|
+
price: z.coerce.string().regex(/^\d+$/).describe("Price (30-decimal raw units)"),
|
|
91
|
+
timeInForce: z.coerce.number().int().describe("TimeInForce enum value"),
|
|
92
|
+
postOnly: z.coerce.boolean().describe("Post-only flag"),
|
|
93
|
+
slippagePct: z.coerce.string().refine(isValidSlippagePct4dp, {
|
|
94
|
+
message: "slippagePct must be an integer in [0, 10000] with 4-decimal precision (1 = 0.01%).",
|
|
95
|
+
}).describe(SLIPPAGE_PCT_4DP_DESC),
|
|
93
96
|
executionFeeToken: z.string().describe("Execution fee token address"),
|
|
94
|
-
leverage: z.number().describe("Leverage"),
|
|
95
|
-
tpSize: z.string().regex(/^\d+$/).optional().describe("TP size raw units"),
|
|
96
|
-
tpPrice: z.string().regex(/^\d+$/).optional().describe("TP price raw units (30 decimals)"),
|
|
97
|
-
slSize: z.string().regex(/^\d+$/).optional().describe("SL size raw units"),
|
|
98
|
-
slPrice: z.string().regex(/^\d+$/).optional().describe("SL price raw units (30 decimals)"),
|
|
99
|
-
tradingFee: z.string().regex(/^\d+$/).describe("Trading fee raw units"),
|
|
97
|
+
leverage: z.coerce.number().describe("Leverage"),
|
|
98
|
+
tpSize: z.coerce.string().regex(/^\d+$/).optional().describe("TP size raw units"),
|
|
99
|
+
tpPrice: z.coerce.string().regex(/^\d+$/).optional().describe("TP price raw units (30 decimals)"),
|
|
100
|
+
slSize: z.coerce.string().regex(/^\d+$/).optional().describe("SL size raw units"),
|
|
101
|
+
slPrice: z.coerce.string().regex(/^\d+$/).optional().describe("SL price raw units (30 decimals)"),
|
|
102
|
+
tradingFee: z.coerce.string().regex(/^\d+$/).describe("Trading fee raw units"),
|
|
100
103
|
marketId: z.string().describe("Market ID"),
|
|
101
104
|
},
|
|
102
105
|
handler: async (args) => {
|
package/dist/tools/setTpSl.js
CHANGED
|
@@ -2,22 +2,25 @@ import { z } from "zod";
|
|
|
2
2
|
import { resolveClient } from "../auth/resolveClient.js";
|
|
3
3
|
import { setPositionTpSl } from "../services/tradeService.js";
|
|
4
4
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
5
|
+
import { isValidSlippagePct4dp, SLIPPAGE_PCT_4DP_DESC } from "../utils/slippage.js";
|
|
5
6
|
export const setTpSlTool = {
|
|
6
7
|
name: "set_tp_sl",
|
|
7
8
|
description: "Create TP/SL order using SDK-native parameters.",
|
|
8
9
|
schema: {
|
|
9
10
|
poolId: z.string().describe("Pool ID"),
|
|
10
11
|
positionId: z.string().describe("Position ID"),
|
|
11
|
-
direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT"),
|
|
12
|
-
leverage: z.number().describe("Leverage"),
|
|
12
|
+
direction: z.coerce.number().pipe(z.union([z.literal(0), z.literal(1)])).describe("0 = LONG, 1 = SHORT"),
|
|
13
|
+
leverage: z.coerce.number().describe("Leverage"),
|
|
13
14
|
executionFeeToken: z.string().describe("Execution fee token address"),
|
|
14
|
-
tpTriggerType: z.number().int().min(0).max(2).describe("TP trigger type"),
|
|
15
|
-
slTriggerType: z.number().int().min(0).max(2).describe("SL trigger type"),
|
|
16
|
-
slippagePct: z.string().
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
tpTriggerType: z.coerce.number().int().min(0).max(2).describe("TP trigger type"),
|
|
16
|
+
slTriggerType: z.coerce.number().int().min(0).max(2).describe("SL trigger type"),
|
|
17
|
+
slippagePct: z.coerce.string().refine(isValidSlippagePct4dp, {
|
|
18
|
+
message: "slippagePct must be an integer in [0, 10000] with 4-decimal precision (1 = 0.01%).",
|
|
19
|
+
}).describe(SLIPPAGE_PCT_4DP_DESC),
|
|
20
|
+
tpPrice: z.coerce.string().regex(/^\d+$/).optional().describe("TP price raw units (30 decimals)"),
|
|
21
|
+
tpSize: z.coerce.string().regex(/^\d+$/).optional().describe("TP size raw units"),
|
|
22
|
+
slPrice: z.coerce.string().regex(/^\d+$/).optional().describe("SL price raw units (30 decimals)"),
|
|
23
|
+
slSize: z.coerce.string().regex(/^\d+$/).optional().describe("SL size raw units"),
|
|
21
24
|
},
|
|
22
25
|
handler: async (args) => {
|
|
23
26
|
try {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const SLIPPAGE_PCT_4DP_RE = /^\d+$/;
|
|
2
|
+
export const SLIPPAGE_PCT_4DP_MAX = 10000n;
|
|
3
|
+
export const SLIPPAGE_PCT_4DP_DESC = "Slippage in 4-decimal precision raw units (1 = 0.01%, 10000 = 100%)";
|
|
4
|
+
export function isValidSlippagePct4dp(value) {
|
|
5
|
+
if (!SLIPPAGE_PCT_4DP_RE.test(value))
|
|
6
|
+
return false;
|
|
7
|
+
return BigInt(value) <= SLIPPAGE_PCT_4DP_MAX;
|
|
8
|
+
}
|
|
9
|
+
export function normalizeSlippagePct4dp(value, label = "slippagePct") {
|
|
10
|
+
const raw = String(value ?? "").trim();
|
|
11
|
+
if (!isValidSlippagePct4dp(raw)) {
|
|
12
|
+
throw new Error(`${label} must be an integer in [0, 10000] with 4-decimal precision (1 = 0.01%).`);
|
|
13
|
+
}
|
|
14
|
+
return raw;
|
|
15
|
+
}
|
package/dist/utils/units.js
CHANGED
|
@@ -23,10 +23,18 @@ export function ensureUnits(value, decimals, label = "value") {
|
|
|
23
23
|
throw new Error(`${label} is required.`);
|
|
24
24
|
if (!DECIMAL_RE.test(str))
|
|
25
25
|
throw new Error(`${label} must be a numeric string.`);
|
|
26
|
-
|
|
26
|
+
// If it's already a very large integer (e.g. > 12 digits or > decimals digits),
|
|
27
|
+
// assume it's already in the smallest unit (Wei/Raw).
|
|
28
|
+
if (!str.includes(".") && (str.length > 12 || str.length > decimals)) {
|
|
29
|
+
return str;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
27
32
|
return parseUnits(str, decimals).toString();
|
|
28
33
|
}
|
|
29
|
-
|
|
34
|
+
catch (e) {
|
|
35
|
+
console.warn(`[ensureUnits] parseUnits failed for ${label}: ${str}. Returning raw.`);
|
|
36
|
+
return str;
|
|
37
|
+
}
|
|
30
38
|
}
|
|
31
39
|
export function parseHumanUnits(value, decimals, label = "value") {
|
|
32
40
|
const str = String(value).trim();
|