@rhea-finance/cross-chain-aggregation-dex 0.2.0 → 0.2.2
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/index.d.mts +195 -18
- package/dist/index.d.ts +195 -18
- package/dist/index.js +1181 -288
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1177 -289
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -10,31 +10,12 @@ function requiresRecipientInExecute(params) {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
// src/utils/errorMessages.ts
|
|
13
|
+
var TRANSACTION_EXECUTION_ERROR_MESSAGE = "Transaction execution error occurred. Please contact support";
|
|
13
14
|
var ErrorMessages = {
|
|
14
|
-
// Parameter validation errors
|
|
15
|
-
MISSING_PARAMS: "Required parameters missing",
|
|
16
|
-
MISSING_TOKEN_ADDRESS: "Token address required",
|
|
17
|
-
INVALID_TOKEN_ADDRESS: "Invalid token address",
|
|
18
|
-
MISSING_USER_ADDRESS: "User address required",
|
|
19
|
-
INVALID_USER_ADDRESS: "Invalid user address",
|
|
20
15
|
// Quote errors
|
|
21
16
|
QUOTE_FAILED: "Failed to get quote",
|
|
22
|
-
QUOTE_INVALID: "Invalid quote",
|
|
23
|
-
QUOTE_NO_ROUTE: "No route found",
|
|
24
|
-
QUOTE_EXPIRED: "Quote expired, please refresh",
|
|
25
17
|
// Execution errors
|
|
26
|
-
EXECUTE_FAILED: "Transaction failed"
|
|
27
|
-
EXECUTE_INVALID_QUOTE: "Invalid quote, please refresh",
|
|
28
|
-
EXECUTE_INSUFFICIENT_BALANCE: "Insufficient balance",
|
|
29
|
-
EXECUTE_INSUFFICIENT_LIQUIDITY: "Insufficient liquidity",
|
|
30
|
-
EXECUTE_SLIPPAGE_TOO_HIGH: "Price changed, please refresh",
|
|
31
|
-
// Network/API errors
|
|
32
|
-
NETWORK_ERROR: "Network error, please try again",
|
|
33
|
-
API_ERROR: "Service temporarily unavailable",
|
|
34
|
-
// Gas/Transaction errors
|
|
35
|
-
GAS_ESTIMATE_FAILED: "Unable to estimate transaction fee",
|
|
36
|
-
GAS_PRICE_FAILED: "Failed to get transaction fee",
|
|
37
|
-
TRANSACTION_REVERTED: "Transaction would fail, please refresh"
|
|
18
|
+
EXECUTE_FAILED: "Transaction failed"
|
|
38
19
|
};
|
|
39
20
|
function getErrorMessage(error, fallback = ErrorMessages.EXECUTE_FAILED) {
|
|
40
21
|
if (!error) return fallback;
|
|
@@ -43,41 +24,70 @@ function getErrorMessage(error, fallback = ErrorMessages.EXECUTE_FAILED) {
|
|
|
43
24
|
}
|
|
44
25
|
return fallback;
|
|
45
26
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
27
|
+
function processErrorMessage(errorMessage) {
|
|
28
|
+
const errorString = errorMessage.toLowerCase();
|
|
29
|
+
const userActionKeywords = [
|
|
30
|
+
"user",
|
|
31
|
+
"rejected",
|
|
32
|
+
"cancelled",
|
|
33
|
+
"cancel",
|
|
34
|
+
"denied",
|
|
35
|
+
"undefined",
|
|
36
|
+
"null"
|
|
37
|
+
];
|
|
38
|
+
const containsUserActionKeyword = userActionKeywords.some(
|
|
39
|
+
(keyword) => errorString.includes(keyword)
|
|
40
|
+
);
|
|
41
|
+
if (containsUserActionKeyword) {
|
|
42
|
+
return errorMessage;
|
|
62
43
|
}
|
|
63
|
-
|
|
44
|
+
return TRANSACTION_EXECUTION_ERROR_MESSAGE;
|
|
45
|
+
}
|
|
46
|
+
function normalizeErrorForIntents(error) {
|
|
47
|
+
if (!error) return ErrorMessages.QUOTE_FAILED;
|
|
48
|
+
const errorLower = error.toLowerCase();
|
|
49
|
+
if (errorLower.includes("quote") || errorLower.includes("route") || errorLower.includes("missing") || errorLower.includes("required") || errorLower.includes("invalid") || errorLower.includes("balance") || errorLower.includes("liquidity") || errorLower.includes("gas") || errorLower.includes("network")) {
|
|
64
50
|
return ErrorMessages.QUOTE_FAILED;
|
|
65
51
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
return ErrorMessages.EXECUTE_INSUFFICIENT_BALANCE;
|
|
71
|
-
}
|
|
72
|
-
if (errorLower.includes("liquidity") || errorLower.includes("slippage")) {
|
|
73
|
-
return ErrorMessages.EXECUTE_SLIPPAGE_TOO_HIGH;
|
|
74
|
-
}
|
|
75
|
-
if (errorLower.includes("gas")) {
|
|
76
|
-
if (errorLower.includes("price")) return ErrorMessages.GAS_PRICE_FAILED;
|
|
77
|
-
return ErrorMessages.GAS_ESTIMATE_FAILED;
|
|
78
|
-
}
|
|
52
|
+
return processErrorMessage(error);
|
|
53
|
+
}
|
|
54
|
+
function normalizeError(error) {
|
|
55
|
+
if (!error) return ErrorMessages.EXECUTE_FAILED;
|
|
79
56
|
return error;
|
|
80
57
|
}
|
|
58
|
+
function formatErrorMessage({
|
|
59
|
+
error,
|
|
60
|
+
fallbackMessage,
|
|
61
|
+
originAsset,
|
|
62
|
+
friendly
|
|
63
|
+
}) {
|
|
64
|
+
let messageStr = "";
|
|
65
|
+
if (error) {
|
|
66
|
+
if (typeof error === "string") {
|
|
67
|
+
messageStr = error;
|
|
68
|
+
} else if (typeof error === "object") {
|
|
69
|
+
messageStr = error?.message || error?.error || JSON.stringify(error);
|
|
70
|
+
} else {
|
|
71
|
+
messageStr = String(error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!messageStr || messageStr === "null" || messageStr === "undefined") {
|
|
75
|
+
messageStr = fallbackMessage || "Transaction execution error occurred. Please contact support";
|
|
76
|
+
}
|
|
77
|
+
if (messageStr?.includes("low") && originAsset) {
|
|
78
|
+
return "Amount is too low for bridge.";
|
|
79
|
+
} else if (messageStr?.includes("(")) {
|
|
80
|
+
const index = messageStr.indexOf("(");
|
|
81
|
+
return friendly ? processErrorMessage(messageStr.slice(0, index)) : messageStr.slice(0, index);
|
|
82
|
+
} else if (messageStr?.includes("timed out") || messageStr?.includes("not allowed")) {
|
|
83
|
+
return "transaction has been cancelled";
|
|
84
|
+
} else if ((messageStr?.toLowerCase().includes("tokenin") || messageStr?.toLowerCase().includes("tokenout")) && messageStr?.toLowerCase().includes("not valid")) {
|
|
85
|
+
return "Failed to get quote";
|
|
86
|
+
} else if (messageStr?.toLowerCase().includes("liquidity") && messageStr?.toLowerCase().includes("available")) {
|
|
87
|
+
return "Failed to get quote";
|
|
88
|
+
}
|
|
89
|
+
return friendly ? processErrorMessage(messageStr) : messageStr;
|
|
90
|
+
}
|
|
81
91
|
|
|
82
92
|
// src/utils/logger.ts
|
|
83
93
|
var LOG_LEVELS = {
|
|
@@ -400,7 +410,7 @@ var NearSmartRouter = class {
|
|
|
400
410
|
amountOut: "0",
|
|
401
411
|
minAmountOut: "0",
|
|
402
412
|
routes: [],
|
|
403
|
-
error: ErrorMessages.
|
|
413
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
404
414
|
};
|
|
405
415
|
}
|
|
406
416
|
const normalizedTokenIn = normalizeTokenId(
|
|
@@ -420,7 +430,7 @@ var NearSmartRouter = class {
|
|
|
420
430
|
amountOut: "0",
|
|
421
431
|
minAmountOut: "0",
|
|
422
432
|
routes: [],
|
|
423
|
-
error: ErrorMessages.
|
|
433
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
424
434
|
};
|
|
425
435
|
}
|
|
426
436
|
const slippageBps = convertSlippageToBasisPoints(slippage);
|
|
@@ -441,7 +451,7 @@ var NearSmartRouter = class {
|
|
|
441
451
|
amountOut: "0",
|
|
442
452
|
minAmountOut: "0",
|
|
443
453
|
routes: [],
|
|
444
|
-
error:
|
|
454
|
+
error: response?.result_msg || response?.result_message || ErrorMessages.QUOTE_FAILED
|
|
445
455
|
};
|
|
446
456
|
}
|
|
447
457
|
const { routes: serverRoutes, amount_out } = response.result_data;
|
|
@@ -480,7 +490,7 @@ var NearSmartRouter = class {
|
|
|
480
490
|
amountOut: "0",
|
|
481
491
|
minAmountOut: "0",
|
|
482
492
|
routes: [],
|
|
483
|
-
error:
|
|
493
|
+
error: error?.message || ErrorMessages.QUOTE_FAILED
|
|
484
494
|
};
|
|
485
495
|
}
|
|
486
496
|
}
|
|
@@ -493,7 +503,7 @@ var NearSmartRouter = class {
|
|
|
493
503
|
if (!quote.success || !quote.routes.length) {
|
|
494
504
|
return {
|
|
495
505
|
success: false,
|
|
496
|
-
error: ErrorMessages.
|
|
506
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
497
507
|
};
|
|
498
508
|
}
|
|
499
509
|
const swapActions = [];
|
|
@@ -512,7 +522,7 @@ var NearSmartRouter = class {
|
|
|
512
522
|
if (!swapActions.length) {
|
|
513
523
|
return {
|
|
514
524
|
success: false,
|
|
515
|
-
error: ErrorMessages.
|
|
525
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
516
526
|
};
|
|
517
527
|
}
|
|
518
528
|
const finalRecipient = depositAddress || recipient;
|
|
@@ -542,7 +552,6 @@ var NearSmartRouter = class {
|
|
|
542
552
|
},
|
|
543
553
|
gas: "50000000000000",
|
|
544
554
|
expandDeposit: "1250000000000000000000"
|
|
545
|
-
// 0.00125 NEAR
|
|
546
555
|
});
|
|
547
556
|
}
|
|
548
557
|
transactions.push({
|
|
@@ -598,7 +607,6 @@ var NearSmartRouter = class {
|
|
|
598
607
|
msg: JSON.stringify(swapMsg)
|
|
599
608
|
},
|
|
600
609
|
gas: "250",
|
|
601
|
-
// NEP-141 requires attaching 1 yoctoNEAR for certain calls.
|
|
602
610
|
expandDeposit: "1"
|
|
603
611
|
});
|
|
604
612
|
const result = await this.nearChainAdapter.call({
|
|
@@ -681,7 +689,7 @@ var AggregateDexRouter = class {
|
|
|
681
689
|
amountOut: "0",
|
|
682
690
|
minAmountOut: "0",
|
|
683
691
|
routes: [],
|
|
684
|
-
error: ErrorMessages.
|
|
692
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
685
693
|
};
|
|
686
694
|
}
|
|
687
695
|
const { tokenIn, tokenOut, amountIn, slippage, sender, recipient } = params;
|
|
@@ -694,7 +702,7 @@ var AggregateDexRouter = class {
|
|
|
694
702
|
amountOut: "0",
|
|
695
703
|
minAmountOut: "0",
|
|
696
704
|
routes: [],
|
|
697
|
-
error: ErrorMessages.
|
|
705
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
698
706
|
};
|
|
699
707
|
}
|
|
700
708
|
if (!tokenIn?.address || !tokenOut?.address) {
|
|
@@ -706,7 +714,7 @@ var AggregateDexRouter = class {
|
|
|
706
714
|
amountOut: "0",
|
|
707
715
|
minAmountOut: "0",
|
|
708
716
|
routes: [],
|
|
709
|
-
error: ErrorMessages.
|
|
717
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
710
718
|
};
|
|
711
719
|
}
|
|
712
720
|
const normalizedTokenIn = normalizeTokenId(
|
|
@@ -726,7 +734,7 @@ var AggregateDexRouter = class {
|
|
|
726
734
|
amountOut: "0",
|
|
727
735
|
minAmountOut: "0",
|
|
728
736
|
routes: [],
|
|
729
|
-
error: ErrorMessages.
|
|
737
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
730
738
|
};
|
|
731
739
|
}
|
|
732
740
|
const slippageBps = convertSlippageToBasisPoints(slippage);
|
|
@@ -785,7 +793,7 @@ var AggregateDexRouter = class {
|
|
|
785
793
|
amountOut: "0",
|
|
786
794
|
minAmountOut: "0",
|
|
787
795
|
routes: [],
|
|
788
|
-
error:
|
|
796
|
+
error: error?.message || ErrorMessages.QUOTE_FAILED
|
|
789
797
|
};
|
|
790
798
|
}
|
|
791
799
|
}
|
|
@@ -794,7 +802,7 @@ var AggregateDexRouter = class {
|
|
|
794
802
|
*/
|
|
795
803
|
async finalizeQuote(params, depositAddress) {
|
|
796
804
|
if (!requiresRecipient(params)) {
|
|
797
|
-
throw new Error(ErrorMessages.
|
|
805
|
+
throw new Error(ErrorMessages.QUOTE_FAILED);
|
|
798
806
|
}
|
|
799
807
|
return await this.quote({
|
|
800
808
|
...params,
|
|
@@ -866,26 +874,26 @@ var AggregateDexRouter = class {
|
|
|
866
874
|
if (!requiresRecipientInExecute(params)) {
|
|
867
875
|
return {
|
|
868
876
|
success: false,
|
|
869
|
-
error: ErrorMessages.
|
|
877
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
870
878
|
};
|
|
871
879
|
}
|
|
872
880
|
const { quote, sender, receiveUser } = params;
|
|
873
881
|
if (!quote.success) {
|
|
874
882
|
return {
|
|
875
883
|
success: false,
|
|
876
|
-
error: ErrorMessages.
|
|
884
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
877
885
|
};
|
|
878
886
|
}
|
|
879
887
|
if (!receiveUser || receiveUser.trim() === "") {
|
|
880
888
|
return {
|
|
881
889
|
success: false,
|
|
882
|
-
error: ErrorMessages.
|
|
890
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
883
891
|
};
|
|
884
892
|
}
|
|
885
893
|
if (receiveUser.startsWith("0x") && receiveUser.length === 42) {
|
|
886
894
|
return {
|
|
887
895
|
success: false,
|
|
888
|
-
error: ErrorMessages.
|
|
896
|
+
error: ErrorMessages.QUOTE_FAILED
|
|
889
897
|
};
|
|
890
898
|
}
|
|
891
899
|
const slippage = quote.slippage || 5e-3;
|
|
@@ -917,7 +925,7 @@ var AggregateDexRouter = class {
|
|
|
917
925
|
} catch (error) {
|
|
918
926
|
return {
|
|
919
927
|
success: false,
|
|
920
|
-
error:
|
|
928
|
+
error: error?.message || ErrorMessages.QUOTE_FAILED
|
|
921
929
|
};
|
|
922
930
|
}
|
|
923
931
|
const routerMsg = finalQuote.routerMsg;
|
|
@@ -1120,7 +1128,8 @@ var AggregateDexRouter = class {
|
|
|
1120
1128
|
} catch (error) {
|
|
1121
1129
|
return {
|
|
1122
1130
|
success: false,
|
|
1123
|
-
|
|
1131
|
+
// ✅ 预交易阶段:保留原始错误信息,不简化
|
|
1132
|
+
error: error?.message || ErrorMessages.QUOTE_FAILED
|
|
1124
1133
|
};
|
|
1125
1134
|
}
|
|
1126
1135
|
const finalAmountToTransfer = finalQuoteForExecution.amountIn;
|
|
@@ -1288,17 +1297,9 @@ async function estimateGasLimit(gas, to, transactionData, value, sender, chainId
|
|
|
1288
1297
|
reason: estimateError?.reason,
|
|
1289
1298
|
message: estimateError?.message || String(estimateError)
|
|
1290
1299
|
};
|
|
1291
|
-
console.warn("RPC gas estimate failed:", {
|
|
1292
|
-
error: estimateError?.message || String(estimateError),
|
|
1293
|
-
code: estimateError?.code,
|
|
1294
|
-
reason: estimateError?.reason
|
|
1295
|
-
});
|
|
1296
1300
|
}
|
|
1297
1301
|
}
|
|
1298
1302
|
} catch (error) {
|
|
1299
|
-
console.warn("Gas estimation error:", {
|
|
1300
|
-
error: error?.message || String(error)
|
|
1301
|
-
});
|
|
1302
1303
|
}
|
|
1303
1304
|
if (estimatedGasLimit && estimatedGasLimit.gt(0) && hasReliableEstimate) {
|
|
1304
1305
|
return [estimatedGasLimit, true, rpcEstimateError];
|
|
@@ -1384,7 +1385,7 @@ var BitgetRouter = class {
|
|
|
1384
1385
|
amountOut: "0",
|
|
1385
1386
|
minAmountOut: "0",
|
|
1386
1387
|
routes: [],
|
|
1387
|
-
error:
|
|
1388
|
+
error: "Bitget quote failed: Router requires recipient address"
|
|
1388
1389
|
};
|
|
1389
1390
|
}
|
|
1390
1391
|
const { tokenIn, tokenOut, amountIn, slippage, sender, recipient } = params;
|
|
@@ -1397,7 +1398,7 @@ var BitgetRouter = class {
|
|
|
1397
1398
|
amountOut: "0",
|
|
1398
1399
|
minAmountOut: "0",
|
|
1399
1400
|
routes: [],
|
|
1400
|
-
error:
|
|
1401
|
+
error: "Bitget quote failed: Sender and recipient addresses are required"
|
|
1401
1402
|
};
|
|
1402
1403
|
}
|
|
1403
1404
|
if (tokenIn?.address === void 0 || tokenOut?.address === void 0) {
|
|
@@ -1409,7 +1410,7 @@ var BitgetRouter = class {
|
|
|
1409
1410
|
amountOut: "0",
|
|
1410
1411
|
minAmountOut: "0",
|
|
1411
1412
|
routes: [],
|
|
1412
|
-
error:
|
|
1413
|
+
error: "Bitget quote failed: Token addresses are required"
|
|
1413
1414
|
};
|
|
1414
1415
|
}
|
|
1415
1416
|
const normalizedTokenIn = tokenIn.address === "" ? "" : this.normalizeEvmAddress(tokenIn.address);
|
|
@@ -1431,7 +1432,7 @@ var BitgetRouter = class {
|
|
|
1431
1432
|
if (!isBitgetResponseSuccess(response) || !response.data) {
|
|
1432
1433
|
return createQuoteError(
|
|
1433
1434
|
params,
|
|
1434
|
-
|
|
1435
|
+
response.msg || "Bitget API error: Failed to get quote"
|
|
1435
1436
|
);
|
|
1436
1437
|
}
|
|
1437
1438
|
const {
|
|
@@ -1497,21 +1498,23 @@ var BitgetRouter = class {
|
|
|
1497
1498
|
} catch (error) {
|
|
1498
1499
|
return createQuoteError(
|
|
1499
1500
|
params,
|
|
1500
|
-
|
|
1501
|
+
error?.message || "Bitget quote failed: Unknown error"
|
|
1501
1502
|
);
|
|
1502
1503
|
}
|
|
1503
1504
|
}
|
|
1504
1505
|
async executeSwap(params) {
|
|
1505
1506
|
try {
|
|
1506
1507
|
if (!requiresRecipientInExecute(params)) {
|
|
1507
|
-
return createExecuteError(
|
|
1508
|
+
return createExecuteError("Bitget swap failed: Router requires recipient address");
|
|
1508
1509
|
}
|
|
1509
1510
|
const { quote, sender, receiveUser } = params;
|
|
1510
1511
|
if (!quote.success) {
|
|
1511
|
-
return createExecuteError(
|
|
1512
|
+
return createExecuteError(
|
|
1513
|
+
quote.error || "Bitget swap failed: Invalid quote"
|
|
1514
|
+
);
|
|
1512
1515
|
}
|
|
1513
1516
|
if (!receiveUser || receiveUser.trim() === "") {
|
|
1514
|
-
return createExecuteError(
|
|
1517
|
+
return createExecuteError("Bitget swap failed: Recipient address is required");
|
|
1515
1518
|
}
|
|
1516
1519
|
let market;
|
|
1517
1520
|
try {
|
|
@@ -1519,13 +1522,13 @@ var BitgetRouter = class {
|
|
|
1519
1522
|
const routerMsg = JSON.parse(quote.routerMsg);
|
|
1520
1523
|
market = routerMsg.market || "";
|
|
1521
1524
|
} else {
|
|
1522
|
-
return createExecuteError(
|
|
1525
|
+
return createExecuteError("Bitget swap failed: Missing router message data");
|
|
1523
1526
|
}
|
|
1524
1527
|
} catch (error) {
|
|
1525
|
-
return createExecuteError(
|
|
1528
|
+
return createExecuteError("Bitget swap failed: Invalid router message format");
|
|
1526
1529
|
}
|
|
1527
1530
|
if (!market) {
|
|
1528
|
-
return createExecuteError(
|
|
1531
|
+
return createExecuteError("Bitget swap failed: Market information is required");
|
|
1529
1532
|
}
|
|
1530
1533
|
const normalizedTokenIn = this.normalizeEvmAddress(
|
|
1531
1534
|
quote.tokenIn.address
|
|
@@ -1548,9 +1551,8 @@ var BitgetRouter = class {
|
|
|
1548
1551
|
tokenOutDecimals: quote.tokenOut.decimals
|
|
1549
1552
|
});
|
|
1550
1553
|
if (!isBitgetResponseSuccess(reQuoteResponse) || !reQuoteResponse.data) {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
);
|
|
1554
|
+
const errorMsg = reQuoteResponse.msg || "Bitget re-quote failed: No data returned";
|
|
1555
|
+
return createExecuteError(errorMsg);
|
|
1554
1556
|
}
|
|
1555
1557
|
market = reQuoteResponse.data?.market || market;
|
|
1556
1558
|
const swapResponse = await this.bitgetAdapter.swap({
|
|
@@ -1569,11 +1571,13 @@ var BitgetRouter = class {
|
|
|
1569
1571
|
});
|
|
1570
1572
|
if (!isBitgetResponseSuccess(swapResponse) || !swapResponse.data) {
|
|
1571
1573
|
return createExecuteError(
|
|
1572
|
-
|
|
1574
|
+
swapResponse.msg || "Bitget swap failed: No transaction data"
|
|
1573
1575
|
);
|
|
1574
1576
|
}
|
|
1575
1577
|
if (swapResponse.data?.estimateRevert === true) {
|
|
1576
|
-
return createExecuteError(
|
|
1578
|
+
return createExecuteError(
|
|
1579
|
+
swapResponse.msg || "Bitget swap failed: Transaction would revert (slippage or price impact too high)"
|
|
1580
|
+
);
|
|
1577
1581
|
}
|
|
1578
1582
|
let transactionData = swapResponse.data?.calldata || swapResponse.data?.data;
|
|
1579
1583
|
const to = swapResponse.data?.contract || swapResponse.data?.to;
|
|
@@ -1587,14 +1591,18 @@ var BitgetRouter = class {
|
|
|
1587
1591
|
}
|
|
1588
1592
|
const gas = swapResponse.data?.gas || (swapResponse.data?.computeUnits !== void 0 ? String(swapResponse.data.computeUnits) : void 0);
|
|
1589
1593
|
if (!to || !transactionData) {
|
|
1590
|
-
return createExecuteError(
|
|
1594
|
+
return createExecuteError(
|
|
1595
|
+
swapResponse.msg || "Bitget swap failed: Missing transaction data or recipient address"
|
|
1596
|
+
);
|
|
1591
1597
|
}
|
|
1592
1598
|
if (transactionData.length < 10 || !/^0x[0-9a-fA-F]+$/.test(transactionData)) {
|
|
1593
|
-
return createExecuteError(
|
|
1599
|
+
return createExecuteError(
|
|
1600
|
+
swapResponse.msg || "Bitget swap failed: Invalid transaction data format"
|
|
1601
|
+
);
|
|
1594
1602
|
}
|
|
1595
1603
|
if (normalizedTokenIn && normalizedTokenIn !== "") {
|
|
1596
1604
|
if (!this.evmChainAdapter.getBalance) {
|
|
1597
|
-
return createExecuteError(
|
|
1605
|
+
return createExecuteError("Bitget swap failed: Balance check not supported");
|
|
1598
1606
|
}
|
|
1599
1607
|
const tokenBalanceFormatted = await this.evmChainAdapter.getBalance({
|
|
1600
1608
|
address: sender,
|
|
@@ -1604,7 +1612,7 @@ var BitgetRouter = class {
|
|
|
1604
1612
|
const balanceBN = ethers.utils.parseUnits(tokenBalanceFormatted || "0", tokenDecimals);
|
|
1605
1613
|
const amountInBN = ethers.BigNumber.from(quote.amountIn);
|
|
1606
1614
|
if (balanceBN.lt(amountInBN)) {
|
|
1607
|
-
return createExecuteError(
|
|
1615
|
+
return createExecuteError("Bitget swap failed: Insufficient token balance");
|
|
1608
1616
|
}
|
|
1609
1617
|
const currentAllowance = await this.evmChainAdapter.getAllowance({
|
|
1610
1618
|
tokenAddress: normalizedTokenIn,
|
|
@@ -1637,17 +1645,17 @@ var BitgetRouter = class {
|
|
|
1637
1645
|
retryCount++;
|
|
1638
1646
|
}
|
|
1639
1647
|
if (newAllowanceBN.lt(amountInBN)) {
|
|
1640
|
-
return createExecuteError(
|
|
1648
|
+
return createExecuteError("Bitget swap failed: Token approval failed or insufficient");
|
|
1641
1649
|
}
|
|
1642
1650
|
} catch (error) {
|
|
1643
1651
|
return createExecuteError(
|
|
1644
|
-
|
|
1652
|
+
error?.message || "Bitget approval check failed"
|
|
1645
1653
|
);
|
|
1646
1654
|
}
|
|
1647
1655
|
}
|
|
1648
1656
|
} else {
|
|
1649
1657
|
if (!this.evmChainAdapter.getBalance) {
|
|
1650
|
-
return createExecuteError(
|
|
1658
|
+
return createExecuteError("Bitget swap failed: Balance check not supported");
|
|
1651
1659
|
}
|
|
1652
1660
|
const nativeBalanceFormatted = await this.evmChainAdapter.getBalance({
|
|
1653
1661
|
address: sender,
|
|
@@ -1671,7 +1679,7 @@ var BitgetRouter = class {
|
|
|
1671
1679
|
const gasCostEstimate = estimatedGasLimitForBalance.mul(gasPriceEstimate2);
|
|
1672
1680
|
const totalRequired = amountInBN.add(gasCostEstimate);
|
|
1673
1681
|
if (balanceBN.lt(totalRequired)) {
|
|
1674
|
-
return createExecuteError(
|
|
1682
|
+
return createExecuteError("Bitget swap failed: Insufficient balance (including gas fee)");
|
|
1675
1683
|
}
|
|
1676
1684
|
}
|
|
1677
1685
|
if (normalizedTokenIn && normalizedTokenIn !== "") {
|
|
@@ -1683,7 +1691,7 @@ var BitgetRouter = class {
|
|
|
1683
1691
|
const finalAllowanceBN = ethers.BigNumber.from(finalAllowanceCheck);
|
|
1684
1692
|
const amountInBN = ethers.BigNumber.from(quote.amountIn);
|
|
1685
1693
|
if (finalAllowanceBN.lt(amountInBN)) {
|
|
1686
|
-
return createExecuteError(
|
|
1694
|
+
return createExecuteError("Bitget swap failed: Insufficient token allowance");
|
|
1687
1695
|
}
|
|
1688
1696
|
}
|
|
1689
1697
|
const [estimatedGasLimit, hasReliableEstimate, rpcEstimateError] = await estimateGasLimit(
|
|
@@ -1704,10 +1712,13 @@ var BitgetRouter = class {
|
|
|
1704
1712
|
tokenOut: quote.tokenOut.symbol,
|
|
1705
1713
|
rpcError: rpcEstimateError
|
|
1706
1714
|
});
|
|
1707
|
-
|
|
1715
|
+
const errorMsg = rpcEstimateError?.message || rpcEstimateError?.reason || "";
|
|
1716
|
+
return createExecuteError(
|
|
1717
|
+
errorMsg || "Bitget swap failed: Transaction would revert (slippage or price impact too high)"
|
|
1718
|
+
);
|
|
1708
1719
|
}
|
|
1709
1720
|
if (!gas || gas === "0") {
|
|
1710
|
-
return createExecuteError(
|
|
1721
|
+
return createExecuteError("Bitget swap failed: Invalid gas estimate from Bitget");
|
|
1711
1722
|
}
|
|
1712
1723
|
logger.warn("RPC gas estimation failed, using Bitget estimate", {
|
|
1713
1724
|
chainId: this.chainId,
|
|
@@ -1740,7 +1751,7 @@ var BitgetRouter = class {
|
|
|
1740
1751
|
if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas || feeData.maxFeePerGas.lte(0) || feeData.maxPriorityFeePerGas.lte(0)) {
|
|
1741
1752
|
const cachedGasPrice = await getCachedGasPrice();
|
|
1742
1753
|
if (!cachedGasPrice || cachedGasPrice.lte(0)) {
|
|
1743
|
-
return createExecuteError(
|
|
1754
|
+
return createExecuteError("Bitget swap failed: Unable to get valid gas price");
|
|
1744
1755
|
}
|
|
1745
1756
|
feeData.maxFeePerGas = cachedGasPrice;
|
|
1746
1757
|
feeData.maxPriorityFeePerGas = cachedGasPrice.div(10);
|
|
@@ -1773,27 +1784,27 @@ var BitgetRouter = class {
|
|
|
1773
1784
|
} catch (error) {
|
|
1774
1785
|
const cachedGasPrice = await getCachedGasPrice();
|
|
1775
1786
|
if (!cachedGasPrice || cachedGasPrice.lte(0)) {
|
|
1776
|
-
return createExecuteError(
|
|
1787
|
+
return createExecuteError("Bitget swap failed: Unable to get valid gas price");
|
|
1777
1788
|
}
|
|
1778
1789
|
feeData.gasPrice = cachedGasPrice;
|
|
1779
1790
|
}
|
|
1780
1791
|
}
|
|
1781
1792
|
if (!to || !ethers.utils.isAddress(to)) {
|
|
1782
|
-
return createExecuteError(
|
|
1793
|
+
return createExecuteError("Bitget swap failed: Invalid recipient address");
|
|
1783
1794
|
}
|
|
1784
1795
|
if (!transactionData || transactionData.length < 10) {
|
|
1785
|
-
return createExecuteError(
|
|
1796
|
+
return createExecuteError("Bitget swap failed: Invalid transaction data");
|
|
1786
1797
|
}
|
|
1787
1798
|
if (!estimatedGasLimit || estimatedGasLimit.lte(0)) {
|
|
1788
|
-
return createExecuteError(
|
|
1799
|
+
return createExecuteError("Bitget swap failed: Invalid gas limit");
|
|
1789
1800
|
}
|
|
1790
1801
|
if (supportsEip1559) {
|
|
1791
1802
|
if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas || feeData.maxFeePerGas.lte(0) || feeData.maxPriorityFeePerGas.lte(0)) {
|
|
1792
|
-
return createExecuteError(
|
|
1803
|
+
return createExecuteError("Bitget swap failed: Invalid gas fee data (EIP-1559)");
|
|
1793
1804
|
}
|
|
1794
1805
|
} else {
|
|
1795
1806
|
if (!feeData.gasPrice || feeData.gasPrice.lte(0)) {
|
|
1796
|
-
return createExecuteError(
|
|
1807
|
+
return createExecuteError("Bitget swap failed: Invalid gas price");
|
|
1797
1808
|
}
|
|
1798
1809
|
}
|
|
1799
1810
|
const txParams = {
|
|
@@ -1833,159 +1844,1000 @@ var BitgetRouter = class {
|
|
|
1833
1844
|
}
|
|
1834
1845
|
}
|
|
1835
1846
|
};
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
targetChain: _targetChain,
|
|
1842
|
-
// Reserved for future use
|
|
1843
|
-
amountIn,
|
|
1844
|
-
slippage,
|
|
1845
|
-
recipient,
|
|
1846
|
-
refundTo,
|
|
1847
|
-
customRecipientMsg,
|
|
1848
|
-
appFees,
|
|
1849
|
-
evmChainId
|
|
1850
|
-
} = params;
|
|
1851
|
-
const {
|
|
1852
|
-
intentsQuotationAdapter,
|
|
1853
|
-
dexRouters,
|
|
1854
|
-
dexRouter,
|
|
1855
|
-
bluechipTokens,
|
|
1856
|
-
configAdapter,
|
|
1857
|
-
currentUserAddress,
|
|
1858
|
-
isIntentsSupportedToken: customIsIntentsSupportedToken
|
|
1859
|
-
} = config;
|
|
1860
|
-
const wrapNearContractId = configAdapter.getWrapNearContractId();
|
|
1861
|
-
const routers = dexRouters || (dexRouter ? [dexRouter] : []);
|
|
1862
|
-
const userAddress = currentUserAddress || recipient;
|
|
1863
|
-
if (!userAddress) {
|
|
1864
|
-
throw new Error(ErrorMessages.MISSING_USER_ADDRESS);
|
|
1847
|
+
var OkxRouter = class {
|
|
1848
|
+
constructor(config) {
|
|
1849
|
+
this.okxAdapter = config.okxAdapter;
|
|
1850
|
+
this.evmChainAdapter = config.evmChainAdapter;
|
|
1851
|
+
this.chainId = config.chainId;
|
|
1865
1852
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1853
|
+
getCapabilities() {
|
|
1854
|
+
return {
|
|
1855
|
+
requiresRecipient: true,
|
|
1856
|
+
requiresFinalizeQuote: false,
|
|
1857
|
+
requiresComplexRegistration: false,
|
|
1858
|
+
supportedChain: "evm"
|
|
1859
|
+
};
|
|
1868
1860
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1861
|
+
getSupportedChain() {
|
|
1862
|
+
return "evm";
|
|
1871
1863
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
const bluechipToken = isEvmChain ? findBestEvmBluechipToken(
|
|
1875
|
-
bluechipTokens,
|
|
1876
|
-
configAdapter.getEvmNativeWrappedTokenAddress?.()
|
|
1877
|
-
) : findBestBluechipToken(bluechipTokens, wrapNearContractId);
|
|
1878
|
-
if (!bluechipToken?.address) {
|
|
1879
|
-
throw new Error(ErrorMessages.QUOTE_FAILED);
|
|
1864
|
+
getChainId() {
|
|
1865
|
+
return this.chainId;
|
|
1880
1866
|
}
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1867
|
+
async quote(params) {
|
|
1868
|
+
try {
|
|
1869
|
+
if (!requiresRecipient(params)) {
|
|
1870
|
+
return createQuoteError(params, "OKX quote failed: Router requires recipient address");
|
|
1871
|
+
}
|
|
1872
|
+
const { tokenIn, tokenOut, amountIn, slippage, sender, recipient } = params;
|
|
1873
|
+
if (!sender || !recipient) {
|
|
1874
|
+
return createQuoteError(params, "OKX quote failed: Sender and recipient addresses are required");
|
|
1875
|
+
}
|
|
1876
|
+
if (tokenIn?.address === void 0 || tokenOut?.address === void 0) {
|
|
1877
|
+
return createQuoteError(params, "OKX quote failed: Token addresses are required");
|
|
1878
|
+
}
|
|
1879
|
+
const normalizedTokenIn = tokenIn.address === "" ? "" : this.normalizeEvmAddress(tokenIn.address);
|
|
1880
|
+
const normalizedTokenOut = tokenOut.address === "" ? "" : this.normalizeEvmAddress(tokenOut.address);
|
|
1881
|
+
const slippageBps = convertSlippageToBasisPoints(slippage);
|
|
1882
|
+
const slippageDecimal = slippageBps / 1e4;
|
|
1883
|
+
const response = await this.okxAdapter.quote({
|
|
1884
|
+
chainId: this.chainId,
|
|
1885
|
+
tokenIn: normalizedTokenIn,
|
|
1886
|
+
tokenOut: normalizedTokenOut,
|
|
1887
|
+
amountIn: String(amountIn),
|
|
1888
|
+
slippage: slippageDecimal,
|
|
1889
|
+
userAddress: sender,
|
|
1890
|
+
tokenInSymbol: tokenIn.symbol,
|
|
1891
|
+
tokenInDecimals: tokenIn.decimals,
|
|
1892
|
+
tokenOutSymbol: tokenOut.symbol,
|
|
1893
|
+
tokenOutDecimals: tokenOut.decimals
|
|
1894
|
+
});
|
|
1895
|
+
const is429Error = response.code === "429" || response.code === 429 || response.msg && response.msg.toLowerCase().includes("rate limit");
|
|
1896
|
+
if (is429Error) {
|
|
1897
|
+
return {
|
|
1898
|
+
success: false,
|
|
1899
|
+
tokenIn: params.tokenIn,
|
|
1900
|
+
tokenOut: params.tokenOut,
|
|
1901
|
+
amountIn: params.amountIn,
|
|
1902
|
+
amountOut: "0",
|
|
1903
|
+
minAmountOut: "0",
|
|
1904
|
+
routes: [],
|
|
1905
|
+
error: "Rate limit exceeded"
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
if (response.code !== "0" && response.code !== 0 && response.code !== void 0) {
|
|
1909
|
+
const errorCode = response.code;
|
|
1910
|
+
const errorMsg = response.msg || "Unknown error";
|
|
1911
|
+
logger.error("OKX quote API error", {
|
|
1912
|
+
code: errorCode,
|
|
1913
|
+
msg: errorMsg,
|
|
1914
|
+
tokenIn: params.tokenIn.symbol,
|
|
1915
|
+
tokenOut: params.tokenOut.symbol,
|
|
1916
|
+
amountIn: params.amountIn,
|
|
1917
|
+
fullResponse: response
|
|
1918
|
+
});
|
|
1919
|
+
return createQuoteError(params, `OKX API error (${errorCode}): ${errorMsg}`);
|
|
1920
|
+
}
|
|
1921
|
+
if (!response.data || response.data.length === 0) {
|
|
1922
|
+
logger.error("OKX quote empty data", {
|
|
1923
|
+
code: response.code,
|
|
1924
|
+
msg: response.msg,
|
|
1925
|
+
hasData: !!response.data,
|
|
1926
|
+
dataLength: response.data?.length || 0,
|
|
1927
|
+
tokenIn: params.tokenIn.symbol,
|
|
1928
|
+
tokenOut: params.tokenOut.symbol,
|
|
1929
|
+
fullResponse: response
|
|
1930
|
+
});
|
|
1931
|
+
return createQuoteError(
|
|
1932
|
+
params,
|
|
1933
|
+
response.msg || "OKX API returned empty data"
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
const quoteData = response.data[0];
|
|
1937
|
+
const fromTokenAmount = quoteData.fromTokenAmount || quoteData.fromAmount;
|
|
1938
|
+
const toTokenAmount = quoteData.toTokenAmount || quoteData.toAmount;
|
|
1939
|
+
const estimateGasFee = quoteData.estimateGasFee || quoteData.estimatedGas;
|
|
1940
|
+
let minToAmount;
|
|
1941
|
+
if (toTokenAmount) {
|
|
1942
|
+
const toAmountBN = ethers.BigNumber.from(toTokenAmount);
|
|
1943
|
+
const slippageAmount = toAmountBN.mul(Math.floor(slippageDecimal * 1e4)).div(1e4);
|
|
1944
|
+
minToAmount = toAmountBN.sub(slippageAmount).toString();
|
|
1945
|
+
} else {
|
|
1946
|
+
minToAmount = "0";
|
|
1947
|
+
}
|
|
1948
|
+
const formattedAmountOut = toTokenAmount ? ethers.BigNumber.from(toTokenAmount).toString() : "0";
|
|
1949
|
+
const formattedMinAmountOut = minToAmount || formattedAmountOut;
|
|
1950
|
+
return {
|
|
1951
|
+
success: true,
|
|
1952
|
+
tokenIn,
|
|
1953
|
+
tokenOut,
|
|
1954
|
+
amountIn: fromTokenAmount || String(amountIn),
|
|
1955
|
+
amountOut: formattedAmountOut,
|
|
1956
|
+
minAmountOut: formattedMinAmountOut,
|
|
1957
|
+
routes: [],
|
|
1958
|
+
gasEstimate: estimateGasFee,
|
|
1959
|
+
routerMsg: JSON.stringify({
|
|
1960
|
+
chainId: this.chainId,
|
|
1961
|
+
adapter: "okx"
|
|
1962
|
+
}),
|
|
1963
|
+
recipient,
|
|
1964
|
+
slippage
|
|
1965
|
+
};
|
|
1966
|
+
} catch (error) {
|
|
1967
|
+
const errorMessage = error?.message || String(error);
|
|
1968
|
+
const isRateLimit = errorMessage?.includes("429") || errorMessage?.toLowerCase().includes("rate limit") || errorMessage?.toLowerCase().includes("too many requests");
|
|
1969
|
+
logger.error("OKX quote exception", {
|
|
1970
|
+
error: errorMessage,
|
|
1971
|
+
isRateLimit,
|
|
1972
|
+
tokenIn: params.tokenIn.symbol,
|
|
1973
|
+
tokenOut: params.tokenOut.symbol,
|
|
1974
|
+
amountIn: params.amountIn,
|
|
1975
|
+
errorStack: error?.stack,
|
|
1976
|
+
fullError: error
|
|
1977
|
+
});
|
|
1978
|
+
const detailedError = isRateLimit ? `OKX API rate limit error: ${errorMessage}. Please try again later.` : `OKX quote failed: ${errorMessage}`;
|
|
1979
|
+
return createQuoteError(params, detailedError);
|
|
1890
1980
|
}
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
} catch (error) {
|
|
1945
|
-
throw new Error(ErrorMessages.QUOTE_FAILED);
|
|
1946
|
-
}
|
|
1981
|
+
}
|
|
1982
|
+
async executeSwap(params) {
|
|
1983
|
+
try {
|
|
1984
|
+
if (!requiresRecipientInExecute(params)) {
|
|
1985
|
+
return createExecuteError("OKX swap failed: Router requires recipient address");
|
|
1986
|
+
}
|
|
1987
|
+
const { quote, sender, receiveUser } = params;
|
|
1988
|
+
if (!quote.success) {
|
|
1989
|
+
return createExecuteError(
|
|
1990
|
+
quote.error || "OKX swap failed: Invalid quote"
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
if (!receiveUser || receiveUser.trim() === "") {
|
|
1994
|
+
return createExecuteError("OKX swap failed: Recipient address is required");
|
|
1995
|
+
}
|
|
1996
|
+
const normalizedTokenIn = this.normalizeEvmAddress(quote.tokenIn.address);
|
|
1997
|
+
const normalizedTokenOut = this.normalizeEvmAddress(
|
|
1998
|
+
quote.tokenOut.address
|
|
1999
|
+
);
|
|
2000
|
+
const slippageBps = convertSlippageToBasisPoints(quote.slippage || 5e-3);
|
|
2001
|
+
const executionSlippage = slippageBps / 1e4;
|
|
2002
|
+
const reQuoteResponse = await this.okxAdapter.quote({
|
|
2003
|
+
chainId: this.chainId,
|
|
2004
|
+
tokenIn: normalizedTokenIn,
|
|
2005
|
+
tokenOut: normalizedTokenOut,
|
|
2006
|
+
amountIn: quote.amountIn,
|
|
2007
|
+
slippage: executionSlippage,
|
|
2008
|
+
userAddress: receiveUser,
|
|
2009
|
+
tokenInSymbol: quote.tokenIn.symbol,
|
|
2010
|
+
tokenInDecimals: quote.tokenIn.decimals,
|
|
2011
|
+
tokenOutSymbol: quote.tokenOut.symbol,
|
|
2012
|
+
tokenOutDecimals: quote.tokenOut.decimals
|
|
2013
|
+
});
|
|
2014
|
+
if (reQuoteResponse.code !== "0" && reQuoteResponse.code !== 0 && reQuoteResponse.code !== void 0) {
|
|
2015
|
+
const errorMsg = reQuoteResponse.msg || `OKX re-quote failed: Error code ${reQuoteResponse.code}`;
|
|
2016
|
+
return createExecuteError(errorMsg);
|
|
2017
|
+
}
|
|
2018
|
+
if (!reQuoteResponse.data || reQuoteResponse.data.length === 0) {
|
|
2019
|
+
const errorMsg = reQuoteResponse.msg || "OKX re-quote failed: No data returned";
|
|
2020
|
+
return createExecuteError(errorMsg);
|
|
2021
|
+
}
|
|
2022
|
+
const reQuoteData = reQuoteResponse.data[0];
|
|
2023
|
+
const toTokenAmount = reQuoteData.toTokenAmount || reQuoteData.toAmount;
|
|
2024
|
+
const okxEstimateGasFee = reQuoteData.estimateGasFee || reQuoteData.estimatedGas;
|
|
2025
|
+
const isCrossChain = receiveUser.toLowerCase() !== sender.toLowerCase();
|
|
2026
|
+
let minAmountOut;
|
|
2027
|
+
if (reQuoteData.minToAmount && reQuoteData.minToAmount !== "0") {
|
|
2028
|
+
minAmountOut = reQuoteData.minToAmount;
|
|
2029
|
+
if (isCrossChain) {
|
|
2030
|
+
logger.info("OKX swap: Using API minToAmount for cross-chain swap", {
|
|
2031
|
+
minToAmount: minAmountOut,
|
|
2032
|
+
toTokenAmount
|
|
2033
|
+
});
|
|
1947
2034
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2035
|
+
} else if (toTokenAmount) {
|
|
2036
|
+
const toAmountBN = ethers.BigNumber.from(toTokenAmount);
|
|
2037
|
+
const effectiveSlippage = isCrossChain ? executionSlippage + 0.02 : executionSlippage;
|
|
2038
|
+
const slippageBps2 = ethers.BigNumber.from(Math.floor(effectiveSlippage * 1e4));
|
|
2039
|
+
const slippageAmount = toAmountBN.mul(slippageBps2).div(1e4);
|
|
2040
|
+
minAmountOut = toAmountBN.sub(slippageAmount).toString();
|
|
2041
|
+
if (isCrossChain) {
|
|
2042
|
+
logger.info("OKX swap: Calculated minAmountOut for cross-chain swap", {
|
|
2043
|
+
toTokenAmount,
|
|
2044
|
+
executionSlippage,
|
|
2045
|
+
effectiveSlippage,
|
|
2046
|
+
slippageBps: slippageBps2.toString(),
|
|
2047
|
+
slippageAmount: slippageAmount.toString(),
|
|
2048
|
+
minAmountOut
|
|
1959
2049
|
});
|
|
1960
|
-
if (intentsQuote.quoteStatus !== "success") {
|
|
1961
|
-
throw new Error(ErrorMessages.QUOTE_FAILED);
|
|
1962
|
-
}
|
|
1963
|
-
return {
|
|
1964
|
-
intentsQuote,
|
|
1965
|
-
preSwapQuote,
|
|
1966
|
-
router,
|
|
1967
|
-
finalAmountOut: intentsQuote.quoteSuccessResult?.quote?.amountOut || "0"
|
|
1968
|
-
};
|
|
1969
|
-
} catch (error) {
|
|
1970
|
-
throw error;
|
|
1971
2050
|
}
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2051
|
+
} else {
|
|
2052
|
+
minAmountOut = "0";
|
|
2053
|
+
}
|
|
2054
|
+
logger.info("OKX swap: calling swap API", {
|
|
2055
|
+
chainId: this.chainId,
|
|
2056
|
+
tokenIn: normalizedTokenIn,
|
|
2057
|
+
tokenOut: normalizedTokenOut,
|
|
2058
|
+
amountIn: quote.amountIn,
|
|
2059
|
+
minAmountOut,
|
|
2060
|
+
fromAddress: sender,
|
|
2061
|
+
toAddress: receiveUser,
|
|
2062
|
+
receiveUser,
|
|
2063
|
+
tokenInSymbol: quote.tokenIn.symbol,
|
|
2064
|
+
tokenInDecimals: quote.tokenIn.decimals,
|
|
2065
|
+
tokenOutSymbol: quote.tokenOut.symbol,
|
|
2066
|
+
tokenOutDecimals: quote.tokenOut.decimals
|
|
2067
|
+
});
|
|
2068
|
+
const swapResponse = await this.okxAdapter.swap({
|
|
2069
|
+
chainId: this.chainId,
|
|
2070
|
+
tokenIn: normalizedTokenIn,
|
|
2071
|
+
tokenOut: normalizedTokenOut,
|
|
2072
|
+
amountIn: quote.amountIn,
|
|
2073
|
+
minAmountOut,
|
|
2074
|
+
slippage: executionSlippage,
|
|
2075
|
+
fromAddress: sender,
|
|
2076
|
+
toAddress: receiveUser,
|
|
2077
|
+
tokenInSymbol: quote.tokenIn.symbol,
|
|
2078
|
+
tokenInDecimals: quote.tokenIn.decimals,
|
|
2079
|
+
tokenOutSymbol: quote.tokenOut.symbol,
|
|
2080
|
+
tokenOutDecimals: quote.tokenOut.decimals
|
|
2081
|
+
});
|
|
2082
|
+
if (swapResponse.code !== "0" && swapResponse.code !== 0 && swapResponse.code !== void 0) {
|
|
2083
|
+
return createExecuteError(
|
|
2084
|
+
swapResponse.msg || `OKX swap failed: Error code ${swapResponse.code}`
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
if (!swapResponse.data) {
|
|
2088
|
+
return createExecuteError(
|
|
2089
|
+
swapResponse.msg || "OKX swap failed: No transaction data"
|
|
2090
|
+
);
|
|
2091
|
+
}
|
|
2092
|
+
let txData = null;
|
|
2093
|
+
if (Array.isArray(swapResponse.data) && swapResponse.data.length > 0) {
|
|
2094
|
+
const firstItem = swapResponse.data[0];
|
|
2095
|
+
txData = firstItem?.tx || firstItem;
|
|
2096
|
+
} else if (swapResponse.data && !Array.isArray(swapResponse.data)) {
|
|
2097
|
+
const data = swapResponse.data;
|
|
2098
|
+
if (data.tx) {
|
|
2099
|
+
txData = data.tx;
|
|
2100
|
+
} else if (data.transaction) {
|
|
2101
|
+
txData = data.transaction;
|
|
2102
|
+
} else {
|
|
2103
|
+
txData = data;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
if (!txData) {
|
|
2107
|
+
logger.error("OKX swap response: no txData found", {
|
|
2108
|
+
swapResponse
|
|
2109
|
+
});
|
|
2110
|
+
return createExecuteError(
|
|
2111
|
+
swapResponse.msg || "OKX swap failed: No transaction data in response"
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
2114
|
+
const dataObj = Array.isArray(swapResponse.data) ? swapResponse.data[0] : swapResponse.data;
|
|
2115
|
+
if (dataObj?.estimateRevert === true || txData?.estimateRevert === true) {
|
|
2116
|
+
logger.error("OKX swap: estimateRevert is true, transaction would fail", {
|
|
2117
|
+
swapResponse,
|
|
2118
|
+
txData
|
|
2119
|
+
});
|
|
2120
|
+
return createExecuteError(
|
|
2121
|
+
swapResponse.msg || "OKX swap failed: Transaction would revert (slippage or price impact too high)"
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
let transactionData = txData.data || "";
|
|
2125
|
+
const to = txData.to || "";
|
|
2126
|
+
let value = txData.value || "0";
|
|
2127
|
+
if (transactionData && !transactionData.startsWith("0x")) {
|
|
2128
|
+
transactionData = "0x" + transactionData;
|
|
2129
|
+
}
|
|
2130
|
+
const isNativeTokenIn = !normalizedTokenIn || normalizedTokenIn === "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
2131
|
+
if (isNativeTokenIn) {
|
|
2132
|
+
value = quote.amountIn;
|
|
2133
|
+
}
|
|
2134
|
+
const gas = txData.gas || txData.gasPrice || okxEstimateGasFee || void 0;
|
|
2135
|
+
if (!to || !transactionData) {
|
|
2136
|
+
logger.error("OKX swap response: missing to or transactionData", {
|
|
2137
|
+
to,
|
|
2138
|
+
hasTransactionData: !!transactionData,
|
|
2139
|
+
txData
|
|
2140
|
+
});
|
|
2141
|
+
return createExecuteError(
|
|
2142
|
+
swapResponse.msg || "OKX swap failed: Missing transaction data or recipient address"
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2145
|
+
if (transactionData.length < 10 || !/^0x[0-9a-fA-F]+$/.test(transactionData)) {
|
|
2146
|
+
logger.error("OKX swap: invalid transaction data format", {
|
|
2147
|
+
transactionDataLength: transactionData.length,
|
|
2148
|
+
transactionDataPrefix: transactionData.substring(0, 20)
|
|
2149
|
+
});
|
|
2150
|
+
return createExecuteError(
|
|
2151
|
+
swapResponse.msg || "OKX swap failed: Invalid transaction data format"
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
logger.info("OKX swap: starting balance and allowance checks");
|
|
2155
|
+
let dexContractAddress;
|
|
2156
|
+
if (normalizedTokenIn && normalizedTokenIn !== "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE") {
|
|
2157
|
+
logger.info("OKX swap: checking ERC20 token balance and allowance", {
|
|
2158
|
+
tokenIn: normalizedTokenIn,
|
|
2159
|
+
sender,
|
|
2160
|
+
to
|
|
2161
|
+
});
|
|
2162
|
+
if (!this.evmChainAdapter.getBalance) {
|
|
2163
|
+
return createExecuteError("OKX swap failed: Balance check not supported");
|
|
2164
|
+
}
|
|
2165
|
+
const tokenBalanceFormatted = await this.evmChainAdapter.getBalance({
|
|
2166
|
+
address: sender,
|
|
2167
|
+
tokenAddress: normalizedTokenIn
|
|
2168
|
+
});
|
|
2169
|
+
const tokenDecimals = quote.tokenIn.decimals || 18;
|
|
2170
|
+
const balanceBN = ethers.utils.parseUnits(
|
|
2171
|
+
tokenBalanceFormatted || "0",
|
|
2172
|
+
tokenDecimals
|
|
2173
|
+
);
|
|
2174
|
+
const amountInBN = ethers.BigNumber.from(quote.amountIn);
|
|
2175
|
+
logger.info("OKX swap: token balance check", {
|
|
2176
|
+
balance: balanceBN.toString(),
|
|
2177
|
+
amountIn: amountInBN.toString(),
|
|
2178
|
+
hasEnoughBalance: balanceBN.gte(amountInBN)
|
|
2179
|
+
});
|
|
2180
|
+
if (balanceBN.lt(amountInBN)) {
|
|
2181
|
+
logger.error("OKX swap: insufficient token balance", {
|
|
2182
|
+
balance: balanceBN.toString(),
|
|
2183
|
+
amountIn: amountInBN.toString()
|
|
2184
|
+
});
|
|
2185
|
+
return createExecuteError("OKX swap failed: Insufficient token balance");
|
|
2186
|
+
}
|
|
2187
|
+
logger.info("OKX swap: getting approve transaction to find dexContractAddress", {
|
|
2188
|
+
tokenIn: normalizedTokenIn,
|
|
2189
|
+
amountIn: quote.amountIn
|
|
2190
|
+
});
|
|
2191
|
+
const approveResponse = await this.okxAdapter.getApproveTransaction({
|
|
2192
|
+
chainId: this.chainId,
|
|
2193
|
+
tokenAddress: normalizedTokenIn,
|
|
2194
|
+
approveAmount: quote.amountIn
|
|
2195
|
+
});
|
|
2196
|
+
if (approveResponse.code !== "0" && approveResponse.code !== 0) {
|
|
2197
|
+
logger.error("OKX swap: failed to get approve transaction", {
|
|
2198
|
+
code: approveResponse.code,
|
|
2199
|
+
msg: approveResponse.msg
|
|
2200
|
+
});
|
|
2201
|
+
return createExecuteError(
|
|
2202
|
+
approveResponse.msg || "Failed to get OKX approve transaction"
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
dexContractAddress = approveResponse.data?.dexContractAddress;
|
|
2206
|
+
if (!dexContractAddress) {
|
|
2207
|
+
logger.error("OKX swap: no dexContractAddress in approve response");
|
|
2208
|
+
return createExecuteError("Failed to get OKX DEX contract address");
|
|
2209
|
+
}
|
|
2210
|
+
logger.info("OKX swap: checking token allowance", {
|
|
2211
|
+
tokenIn: normalizedTokenIn,
|
|
2212
|
+
owner: sender,
|
|
2213
|
+
spender: dexContractAddress
|
|
2214
|
+
});
|
|
2215
|
+
const currentAllowance = await this.evmChainAdapter.getAllowance({
|
|
2216
|
+
tokenAddress: normalizedTokenIn,
|
|
2217
|
+
owner: sender,
|
|
2218
|
+
spender: dexContractAddress
|
|
2219
|
+
});
|
|
2220
|
+
const allowanceBN = ethers.BigNumber.from(currentAllowance);
|
|
2221
|
+
logger.info("OKX swap: token allowance check", {
|
|
2222
|
+
allowance: allowanceBN.toString(),
|
|
2223
|
+
amountIn: amountInBN.toString(),
|
|
2224
|
+
hasEnoughAllowance: allowanceBN.gte(amountInBN),
|
|
2225
|
+
dexContractAddress,
|
|
2226
|
+
sender,
|
|
2227
|
+
tokenIn: normalizedTokenIn
|
|
2228
|
+
});
|
|
2229
|
+
if (allowanceBN.lt(amountInBN)) {
|
|
2230
|
+
logger.warn("OKX swap: insufficient token allowance, need to approve", {
|
|
2231
|
+
allowance: allowanceBN.toString(),
|
|
2232
|
+
amountIn: amountInBN.toString(),
|
|
2233
|
+
dexContractAddress,
|
|
2234
|
+
sender,
|
|
2235
|
+
tokenIn: normalizedTokenIn
|
|
2236
|
+
});
|
|
2237
|
+
if (!approveResponse.data?.data) {
|
|
2238
|
+
logger.error("OKX swap: no approve transaction data");
|
|
2239
|
+
return createExecuteError("Failed to get OKX approve transaction data");
|
|
2240
|
+
}
|
|
2241
|
+
try {
|
|
2242
|
+
const [estimatedGasLimit, hasReliableEstimate] = await estimateGasLimit(
|
|
2243
|
+
approveResponse.data.gasLimit,
|
|
2244
|
+
normalizedTokenIn,
|
|
2245
|
+
approveResponse.data.data,
|
|
2246
|
+
"0",
|
|
2247
|
+
sender,
|
|
2248
|
+
this.chainId,
|
|
2249
|
+
this.evmChainAdapter
|
|
2250
|
+
);
|
|
2251
|
+
const approveTxParams = {
|
|
2252
|
+
to: normalizedTokenIn,
|
|
2253
|
+
data: approveResponse.data.data,
|
|
2254
|
+
value: "0",
|
|
2255
|
+
gasLimit: estimatedGasLimit.toString()
|
|
2256
|
+
};
|
|
2257
|
+
if (isEip1559Chain(this.chainId)) {
|
|
2258
|
+
const feeData2 = await getEip1559FeeData(this.chainId, this.evmChainAdapter);
|
|
2259
|
+
if (feeData2.maxFeePerGas && feeData2.maxPriorityFeePerGas) {
|
|
2260
|
+
approveTxParams.type = 2;
|
|
2261
|
+
approveTxParams.maxFeePerGas = feeData2.maxFeePerGas.toString();
|
|
2262
|
+
approveTxParams.maxPriorityFeePerGas = feeData2.maxPriorityFeePerGas.toString();
|
|
2263
|
+
} else {
|
|
2264
|
+
if (approveResponse.data.gasPrice) {
|
|
2265
|
+
approveTxParams.gasPrice = approveResponse.data.gasPrice;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
} else {
|
|
2269
|
+
if (approveResponse.data.gasPrice) {
|
|
2270
|
+
approveTxParams.gasPrice = approveResponse.data.gasPrice;
|
|
2271
|
+
} else {
|
|
2272
|
+
const estimatedGasPrice = await getGasPriceEstimate(
|
|
2273
|
+
this.chainId,
|
|
2274
|
+
this.evmChainAdapter
|
|
2275
|
+
);
|
|
2276
|
+
approveTxParams.gasPrice = estimatedGasPrice.toString();
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
logger.info("OKX swap: sending approve transaction", {
|
|
2280
|
+
to: normalizedTokenIn,
|
|
2281
|
+
tokenIn: normalizedTokenIn,
|
|
2282
|
+
dexContractAddress,
|
|
2283
|
+
hasData: !!approveResponse.data.data,
|
|
2284
|
+
gasLimit: estimatedGasLimit.toString(),
|
|
2285
|
+
okxGasLimit: approveResponse.data.gasLimit,
|
|
2286
|
+
hasReliableEstimate
|
|
2287
|
+
});
|
|
2288
|
+
const approveResult = await this.evmChainAdapter.sendTransaction(approveTxParams);
|
|
2289
|
+
if (approveResult.status !== "success") {
|
|
2290
|
+
logger.error("OKX swap: approve transaction failed", {
|
|
2291
|
+
status: approveResult.status,
|
|
2292
|
+
message: approveResult.message
|
|
2293
|
+
});
|
|
2294
|
+
return createExecuteError(
|
|
2295
|
+
approveResult.message || "Approve transaction failed"
|
|
2296
|
+
);
|
|
2297
|
+
}
|
|
2298
|
+
logger.info("OKX swap: approve transaction successful", {
|
|
2299
|
+
txHash: approveResult.txHash
|
|
2300
|
+
});
|
|
2301
|
+
let retryCount = 0;
|
|
2302
|
+
while (retryCount < MAX_APPROVAL_RETRIES) {
|
|
2303
|
+
await new Promise(
|
|
2304
|
+
(resolve) => setTimeout(resolve, APPROVAL_RETRY_DELAY_MS * (retryCount + 1))
|
|
2305
|
+
);
|
|
2306
|
+
const newAllowance = await this.evmChainAdapter.getAllowance({
|
|
2307
|
+
tokenAddress: normalizedTokenIn,
|
|
2308
|
+
owner: sender,
|
|
2309
|
+
spender: dexContractAddress
|
|
2310
|
+
});
|
|
2311
|
+
const newAllowanceBN = ethers.BigNumber.from(newAllowance);
|
|
2312
|
+
logger.info("OKX swap: checking approval status", {
|
|
2313
|
+
retryCount,
|
|
2314
|
+
allowance: newAllowanceBN.toString(),
|
|
2315
|
+
amountIn: amountInBN.toString(),
|
|
2316
|
+
hasEnoughAllowance: newAllowanceBN.gte(amountInBN)
|
|
2317
|
+
});
|
|
2318
|
+
if (newAllowanceBN.gte(amountInBN)) {
|
|
2319
|
+
logger.info("OKX swap: approval confirmed");
|
|
2320
|
+
break;
|
|
2321
|
+
}
|
|
2322
|
+
retryCount++;
|
|
2323
|
+
}
|
|
2324
|
+
const finalAllowance = await this.evmChainAdapter.getAllowance({
|
|
2325
|
+
tokenAddress: normalizedTokenIn,
|
|
2326
|
+
owner: sender,
|
|
2327
|
+
spender: dexContractAddress
|
|
2328
|
+
});
|
|
2329
|
+
const finalAllowanceBN = ethers.BigNumber.from(finalAllowance);
|
|
2330
|
+
if (finalAllowanceBN.lt(amountInBN)) {
|
|
2331
|
+
logger.error("OKX swap: approval still insufficient after retries", {
|
|
2332
|
+
allowance: finalAllowanceBN.toString(),
|
|
2333
|
+
amountIn: amountInBN.toString()
|
|
2334
|
+
});
|
|
2335
|
+
return createExecuteError("Token approval failed or insufficient");
|
|
2336
|
+
}
|
|
2337
|
+
} catch (error) {
|
|
2338
|
+
logger.error("OKX swap: approve transaction error", {
|
|
2339
|
+
error: error?.message
|
|
2340
|
+
});
|
|
2341
|
+
return createExecuteError(
|
|
2342
|
+
normalizeError(error?.message) || "Approve transaction failed"
|
|
2343
|
+
);
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
} else {
|
|
2347
|
+
if (!this.evmChainAdapter.getBalance) {
|
|
2348
|
+
return createExecuteError("OKX swap failed: Balance check not supported");
|
|
2349
|
+
}
|
|
2350
|
+
const nativeBalanceFormatted = await this.evmChainAdapter.getBalance({
|
|
2351
|
+
address: sender,
|
|
2352
|
+
tokenAddress: void 0
|
|
2353
|
+
});
|
|
2354
|
+
const balanceBN = ethers.utils.parseEther(nativeBalanceFormatted || "0");
|
|
2355
|
+
const amountInBN = ethers.BigNumber.from(quote.amountIn);
|
|
2356
|
+
const gasPriceEstimate2 = await getGasPriceEstimate(
|
|
2357
|
+
this.chainId,
|
|
2358
|
+
this.evmChainAdapter
|
|
2359
|
+
);
|
|
2360
|
+
const [estimatedGasLimitForBalance] = await estimateGasLimit(
|
|
2361
|
+
gas,
|
|
2362
|
+
to,
|
|
2363
|
+
transactionData,
|
|
2364
|
+
value,
|
|
2365
|
+
sender,
|
|
2366
|
+
this.chainId,
|
|
2367
|
+
this.evmChainAdapter
|
|
2368
|
+
);
|
|
2369
|
+
const gasCostEstimate = estimatedGasLimitForBalance.mul(gasPriceEstimate2);
|
|
2370
|
+
const totalRequired = amountInBN.add(gasCostEstimate);
|
|
2371
|
+
if (balanceBN.lt(totalRequired)) {
|
|
2372
|
+
return createExecuteError("OKX swap failed: Insufficient balance (including gas fee)");
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
if (normalizedTokenIn && normalizedTokenIn !== "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" && dexContractAddress) {
|
|
2376
|
+
const finalAllowanceCheck = await this.evmChainAdapter.getAllowance({
|
|
2377
|
+
tokenAddress: normalizedTokenIn,
|
|
2378
|
+
owner: sender,
|
|
2379
|
+
spender: dexContractAddress
|
|
2380
|
+
});
|
|
2381
|
+
const finalAllowanceBN = ethers.BigNumber.from(finalAllowanceCheck);
|
|
2382
|
+
const amountInBN = ethers.BigNumber.from(quote.amountIn);
|
|
2383
|
+
if (finalAllowanceBN.lt(amountInBN)) {
|
|
2384
|
+
logger.error("OKX swap: final allowance check failed", {
|
|
2385
|
+
allowance: finalAllowanceBN.toString(),
|
|
2386
|
+
amountIn: amountInBN.toString(),
|
|
2387
|
+
dexContractAddress
|
|
2388
|
+
});
|
|
2389
|
+
return createExecuteError("OKX swap failed: Insufficient token allowance");
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
const [rpcEstimatedGasLimit, hasReliableRpcEstimate, rpcEstimateError] = await estimateGasLimit(
|
|
2393
|
+
void 0,
|
|
2394
|
+
to,
|
|
2395
|
+
transactionData,
|
|
2396
|
+
value,
|
|
2397
|
+
sender,
|
|
2398
|
+
this.chainId,
|
|
2399
|
+
this.evmChainAdapter
|
|
2400
|
+
);
|
|
2401
|
+
let finalGasLimit;
|
|
2402
|
+
const MAX_REASONABLE_GAS_LIMIT = ethers.BigNumber.from("500000");
|
|
2403
|
+
const okxGasBN = gas ? ethers.BigNumber.from(gas) : null;
|
|
2404
|
+
const okxEstimateGasFeeBN = okxEstimateGasFee ? ethers.BigNumber.from(okxEstimateGasFee) : null;
|
|
2405
|
+
if (rpcEstimateError) {
|
|
2406
|
+
const isUnpredictableGasLimit = rpcEstimateError.code === "UNPREDICTABLE_GAS_LIMIT" || rpcEstimateError.message && rpcEstimateError.message.includes("UNPREDICTABLE_GAS_LIMIT") || rpcEstimateError.reason && rpcEstimateError.reason.includes("execution reverted");
|
|
2407
|
+
if (isUnpredictableGasLimit) {
|
|
2408
|
+
logger.warn("Blocking transaction due to RPC gas estimation failure", {
|
|
2409
|
+
chainId: this.chainId,
|
|
2410
|
+
tokenIn: quote.tokenIn.symbol,
|
|
2411
|
+
tokenOut: quote.tokenOut.symbol,
|
|
2412
|
+
rpcError: rpcEstimateError
|
|
2413
|
+
});
|
|
2414
|
+
const errorMsg = rpcEstimateError?.message || rpcEstimateError?.reason || "";
|
|
2415
|
+
return createExecuteError(
|
|
2416
|
+
errorMsg || "OKX swap failed: Transaction would revert (slippage or price impact too high)"
|
|
2417
|
+
);
|
|
2418
|
+
}
|
|
2419
|
+
if (okxEstimateGasFeeBN && okxEstimateGasFeeBN.gt(0)) {
|
|
2420
|
+
finalGasLimit = okxEstimateGasFeeBN.gt(MAX_REASONABLE_GAS_LIMIT) ? MAX_REASONABLE_GAS_LIMIT : okxEstimateGasFeeBN;
|
|
2421
|
+
logger.warn("RPC gas estimation failed, using OKX estimateGasFee (capped)", {
|
|
2422
|
+
chainId: this.chainId,
|
|
2423
|
+
tokenIn: quote.tokenIn.symbol,
|
|
2424
|
+
tokenOut: quote.tokenOut.symbol,
|
|
2425
|
+
okxEstimateGasFee: okxEstimateGasFeeBN.toString(),
|
|
2426
|
+
finalGasLimit: finalGasLimit.toString(),
|
|
2427
|
+
rpcError: rpcEstimateError
|
|
2428
|
+
});
|
|
2429
|
+
} else if (okxGasBN && okxGasBN.gt(0)) {
|
|
2430
|
+
finalGasLimit = okxGasBN.gt(MAX_REASONABLE_GAS_LIMIT) ? MAX_REASONABLE_GAS_LIMIT : okxGasBN;
|
|
2431
|
+
logger.warn("RPC gas estimation failed, using OKX gas from swap (capped)", {
|
|
2432
|
+
chainId: this.chainId,
|
|
2433
|
+
tokenIn: quote.tokenIn.symbol,
|
|
2434
|
+
tokenOut: quote.tokenOut.symbol,
|
|
2435
|
+
okxGas: okxGasBN.toString(),
|
|
2436
|
+
finalGasLimit: finalGasLimit.toString(),
|
|
2437
|
+
rpcError: rpcEstimateError
|
|
2438
|
+
});
|
|
2439
|
+
} else {
|
|
2440
|
+
return createExecuteError("OKX swap failed: Unable to determine gas limit");
|
|
2441
|
+
}
|
|
2442
|
+
} else if (hasReliableRpcEstimate && rpcEstimatedGasLimit.gt(0)) {
|
|
2443
|
+
finalGasLimit = rpcEstimatedGasLimit;
|
|
2444
|
+
if (okxGasBN && okxGasBN.gt(rpcEstimatedGasLimit.mul(2))) {
|
|
2445
|
+
logger.warn("OKX gas estimate is much higher than RPC estimate, using RPC estimate", {
|
|
2446
|
+
chainId: this.chainId,
|
|
2447
|
+
tokenIn: quote.tokenIn.symbol,
|
|
2448
|
+
tokenOut: quote.tokenOut.symbol,
|
|
2449
|
+
rpcEstimate: rpcEstimatedGasLimit.toString(),
|
|
2450
|
+
okxGas: okxGasBN.toString(),
|
|
2451
|
+
finalGasLimit: finalGasLimit.toString()
|
|
2452
|
+
});
|
|
2453
|
+
} else {
|
|
2454
|
+
logger.info("Using RPC gas estimate for swap transaction", {
|
|
2455
|
+
chainId: this.chainId,
|
|
2456
|
+
tokenIn: quote.tokenIn.symbol,
|
|
2457
|
+
tokenOut: quote.tokenOut.symbol,
|
|
2458
|
+
rpcEstimate: rpcEstimatedGasLimit.toString(),
|
|
2459
|
+
okxGas: okxGasBN?.toString() || "not provided",
|
|
2460
|
+
finalGasLimit: finalGasLimit.toString()
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
} else {
|
|
2464
|
+
if (okxEstimateGasFeeBN && okxEstimateGasFeeBN.gt(0)) {
|
|
2465
|
+
finalGasLimit = okxEstimateGasFeeBN.gt(MAX_REASONABLE_GAS_LIMIT) ? MAX_REASONABLE_GAS_LIMIT : okxEstimateGasFeeBN;
|
|
2466
|
+
} else if (okxGasBN && okxGasBN.gt(0)) {
|
|
2467
|
+
finalGasLimit = okxGasBN.gt(MAX_REASONABLE_GAS_LIMIT) ? MAX_REASONABLE_GAS_LIMIT : okxGasBN;
|
|
2468
|
+
} else if (rpcEstimatedGasLimit.gt(0)) {
|
|
2469
|
+
finalGasLimit = rpcEstimatedGasLimit;
|
|
2470
|
+
} else {
|
|
2471
|
+
return createExecuteError("OKX swap failed: Unable to determine gas limit");
|
|
2472
|
+
}
|
|
2473
|
+
logger.warn("Gas estimation unreliable, using fallback (capped)", {
|
|
2474
|
+
chainId: this.chainId,
|
|
2475
|
+
tokenIn: quote.tokenIn.symbol,
|
|
2476
|
+
tokenOut: quote.tokenOut.symbol,
|
|
2477
|
+
finalGasLimit: finalGasLimit.toString(),
|
|
2478
|
+
okxGas: okxGasBN?.toString() || "not provided",
|
|
2479
|
+
okxEstimateGasFee: okxEstimateGasFeeBN?.toString() || "not provided"
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
const supportsEip1559 = isEip1559Chain(this.chainId);
|
|
2483
|
+
let gasPriceEstimate;
|
|
2484
|
+
const getCachedGasPrice = async () => {
|
|
2485
|
+
if (!gasPriceEstimate) {
|
|
2486
|
+
gasPriceEstimate = await getGasPriceEstimate(
|
|
2487
|
+
this.chainId,
|
|
2488
|
+
this.evmChainAdapter
|
|
2489
|
+
);
|
|
2490
|
+
}
|
|
2491
|
+
return gasPriceEstimate;
|
|
2492
|
+
};
|
|
2493
|
+
let feeData = {};
|
|
2494
|
+
if (supportsEip1559) {
|
|
2495
|
+
feeData = await getEip1559FeeData(this.chainId, this.evmChainAdapter);
|
|
2496
|
+
if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas || feeData.maxFeePerGas.lte(0) || feeData.maxPriorityFeePerGas.lte(0)) {
|
|
2497
|
+
const cachedGasPrice = await getCachedGasPrice();
|
|
2498
|
+
if (!cachedGasPrice || cachedGasPrice.lte(0)) {
|
|
2499
|
+
return createExecuteError("OKX swap failed: Unable to get valid gas price");
|
|
2500
|
+
}
|
|
2501
|
+
feeData.maxFeePerGas = cachedGasPrice;
|
|
2502
|
+
feeData.maxPriorityFeePerGas = cachedGasPrice.div(10);
|
|
2503
|
+
const minPriorityFee = ethers.utils.parseUnits("1", "gwei");
|
|
2504
|
+
if (feeData.maxPriorityFeePerGas.lt(minPriorityFee)) {
|
|
2505
|
+
feeData.maxPriorityFeePerGas = minPriorityFee;
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
} else {
|
|
2509
|
+
try {
|
|
2510
|
+
const signer = await this.evmChainAdapter.getSigner?.();
|
|
2511
|
+
if (signer?.provider) {
|
|
2512
|
+
const providerFeeData = await signer.provider.getFeeData();
|
|
2513
|
+
if (providerFeeData.gasPrice && providerFeeData.gasPrice.gt(0)) {
|
|
2514
|
+
feeData.gasPrice = providerFeeData.gasPrice;
|
|
2515
|
+
} else {
|
|
2516
|
+
const cachedGasPrice = await getCachedGasPrice();
|
|
2517
|
+
if (!cachedGasPrice || cachedGasPrice.lte(0)) {
|
|
2518
|
+
return createExecuteError("Failed to get valid gas price. Please try again.");
|
|
2519
|
+
}
|
|
2520
|
+
feeData.gasPrice = cachedGasPrice;
|
|
2521
|
+
}
|
|
2522
|
+
} else {
|
|
2523
|
+
const cachedGasPrice = await getCachedGasPrice();
|
|
2524
|
+
if (!cachedGasPrice || cachedGasPrice.lte(0)) {
|
|
2525
|
+
return createExecuteError("Failed to get valid gas price. Please try again.");
|
|
2526
|
+
}
|
|
2527
|
+
feeData.gasPrice = cachedGasPrice;
|
|
2528
|
+
}
|
|
2529
|
+
} catch (error) {
|
|
2530
|
+
const cachedGasPrice = await getCachedGasPrice();
|
|
2531
|
+
if (!cachedGasPrice || cachedGasPrice.lte(0)) {
|
|
2532
|
+
return createExecuteError("OKX swap failed: Unable to get valid gas price");
|
|
2533
|
+
}
|
|
2534
|
+
feeData.gasPrice = cachedGasPrice;
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
if (!to || !ethers.utils.isAddress(to)) {
|
|
2538
|
+
return createExecuteError("OKX swap failed: Invalid recipient address");
|
|
2539
|
+
}
|
|
2540
|
+
if (!transactionData || transactionData.length < 10) {
|
|
2541
|
+
return createExecuteError("OKX swap failed: Invalid transaction data");
|
|
2542
|
+
}
|
|
2543
|
+
if (!finalGasLimit || finalGasLimit.lte(0)) {
|
|
2544
|
+
return createExecuteError("OKX swap failed: Invalid gas limit");
|
|
2545
|
+
}
|
|
2546
|
+
if (supportsEip1559) {
|
|
2547
|
+
if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas || feeData.maxFeePerGas.lte(0) || feeData.maxPriorityFeePerGas.lte(0)) {
|
|
2548
|
+
return createExecuteError("OKX swap failed: Invalid gas fee data (EIP-1559)");
|
|
2549
|
+
}
|
|
2550
|
+
} else {
|
|
2551
|
+
if (!feeData.gasPrice || feeData.gasPrice.lte(0)) {
|
|
2552
|
+
return createExecuteError("OKX swap failed: Invalid gas price");
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
const txParams = {
|
|
2556
|
+
to,
|
|
2557
|
+
data: transactionData,
|
|
2558
|
+
value: value || "0",
|
|
2559
|
+
gasLimit: finalGasLimit.toString()
|
|
2560
|
+
};
|
|
2561
|
+
if (supportsEip1559 && feeData.maxFeePerGas && feeData.maxPriorityFeePerGas) {
|
|
2562
|
+
txParams.type = 2;
|
|
2563
|
+
txParams.maxFeePerGas = feeData.maxFeePerGas.toString();
|
|
2564
|
+
txParams.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas.toString();
|
|
2565
|
+
} else if (feeData.gasPrice) {
|
|
2566
|
+
txParams.gasPrice = feeData.gasPrice.toString();
|
|
2567
|
+
}
|
|
2568
|
+
logger.info("OKX sending transaction to wallet", {
|
|
2569
|
+
to,
|
|
2570
|
+
hasData: !!transactionData,
|
|
2571
|
+
dataLength: transactionData?.length,
|
|
2572
|
+
value,
|
|
2573
|
+
gasLimit: finalGasLimit.toString(),
|
|
2574
|
+
supportsEip1559,
|
|
2575
|
+
feeData: supportsEip1559 ? {
|
|
2576
|
+
maxFeePerGas: feeData.maxFeePerGas?.toString(),
|
|
2577
|
+
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas?.toString()
|
|
2578
|
+
} : { gasPrice: feeData.gasPrice?.toString() }
|
|
2579
|
+
});
|
|
2580
|
+
const result = await this.evmChainAdapter.sendTransaction(txParams);
|
|
2581
|
+
logger.info("OKX transaction result", {
|
|
2582
|
+
status: result.status,
|
|
2583
|
+
txHash: result.txHash,
|
|
2584
|
+
message: result.message
|
|
2585
|
+
});
|
|
2586
|
+
if (result.status === "success") {
|
|
2587
|
+
return {
|
|
2588
|
+
success: true,
|
|
2589
|
+
txHash: result.txHash,
|
|
2590
|
+
txHashArray: result.txHash ? [result.txHash] : []
|
|
2591
|
+
};
|
|
2592
|
+
} else {
|
|
2593
|
+
logger.error("OKX transaction failed", {
|
|
2594
|
+
status: result.status,
|
|
2595
|
+
txHash: result.txHash,
|
|
2596
|
+
message: result.message
|
|
2597
|
+
});
|
|
2598
|
+
return createExecuteError(
|
|
2599
|
+
normalizeError(result.message) || ErrorMessages.EXECUTE_FAILED
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
} catch (error) {
|
|
2603
|
+
logger.error("OKX execute swap error:", error);
|
|
2604
|
+
return createExecuteError(
|
|
2605
|
+
normalizeError(error?.message) || ErrorMessages.EXECUTE_FAILED
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
normalizeEvmAddress(address) {
|
|
2610
|
+
if (!address) return address;
|
|
2611
|
+
try {
|
|
2612
|
+
return ethers.utils.getAddress(address);
|
|
2613
|
+
} catch (e) {
|
|
2614
|
+
const addr = address.startsWith("0x") ? address.slice(2) : address;
|
|
2615
|
+
return "0x" + addr.toLowerCase();
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
};
|
|
2619
|
+
async function completeQuote(params, config) {
|
|
2620
|
+
const {
|
|
2621
|
+
sourceToken,
|
|
2622
|
+
targetToken,
|
|
2623
|
+
sourceChain,
|
|
2624
|
+
targetChain: _targetChain,
|
|
2625
|
+
// Reserved for future use
|
|
2626
|
+
amountIn,
|
|
2627
|
+
slippage,
|
|
2628
|
+
recipient,
|
|
2629
|
+
refundTo,
|
|
2630
|
+
customRecipientMsg,
|
|
2631
|
+
appFees,
|
|
2632
|
+
evmChainId
|
|
2633
|
+
} = params;
|
|
2634
|
+
const {
|
|
2635
|
+
intentsQuotationAdapter,
|
|
2636
|
+
dexRouters,
|
|
2637
|
+
dexRouter,
|
|
2638
|
+
bluechipTokens,
|
|
2639
|
+
configAdapter,
|
|
2640
|
+
currentUserAddress,
|
|
2641
|
+
isIntentsSupportedToken: customIsIntentsSupportedToken
|
|
2642
|
+
} = config;
|
|
2643
|
+
const wrapNearContractId = configAdapter.getWrapNearContractId();
|
|
2644
|
+
const routers = dexRouters || (dexRouter ? [dexRouter] : []);
|
|
2645
|
+
const userAddress = currentUserAddress || recipient;
|
|
2646
|
+
if (!userAddress) {
|
|
2647
|
+
throw new Error(ErrorMessages.QUOTE_FAILED);
|
|
2648
|
+
}
|
|
2649
|
+
if (sourceToken?.address === void 0) {
|
|
2650
|
+
throw new Error(ErrorMessages.QUOTE_FAILED);
|
|
2651
|
+
}
|
|
2652
|
+
if (targetToken?.address === void 0) {
|
|
2653
|
+
throw new Error(ErrorMessages.QUOTE_FAILED);
|
|
2654
|
+
}
|
|
2655
|
+
const isEvmChain = evmChainId !== void 0 || sourceToken.chain === "evm" || sourceChain === "evm";
|
|
2656
|
+
const isTokenIntentsSupported = isEvmChain && sourceToken.platform === "nearIntents" || (customIsIntentsSupportedToken ? customIsIntentsSupportedToken(sourceToken) : isEvmChain ? isEvmIntentsSupportedToken(sourceToken, bluechipTokens) : isNearIntentsSupportedToken(sourceToken, bluechipTokens));
|
|
2657
|
+
const bluechipToken = isEvmChain ? findBestEvmBluechipToken(
|
|
2658
|
+
bluechipTokens,
|
|
2659
|
+
configAdapter.getEvmNativeWrappedTokenAddress?.()
|
|
2660
|
+
) : findBestBluechipToken(bluechipTokens, wrapNearContractId);
|
|
2661
|
+
if (!bluechipToken?.address) {
|
|
2662
|
+
throw new Error(ErrorMessages.QUOTE_FAILED);
|
|
2663
|
+
}
|
|
2664
|
+
async function quoteWithRetry(router, quoteParams, _routerType, _routerName, maxRetries = 2, initialDelay = 1e3) {
|
|
2665
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
2666
|
+
try {
|
|
2667
|
+
const quote = await router.quote(quoteParams);
|
|
2668
|
+
if (quote.success) {
|
|
2669
|
+
return quote;
|
|
2670
|
+
}
|
|
2671
|
+
const isRateLimit = quote.error?.includes("429") || quote.error?.toLowerCase().includes("rate limit") || quote.error?.toLowerCase().includes("too many requests");
|
|
2672
|
+
if (!isRateLimit || attempt === maxRetries) {
|
|
2673
|
+
return quote;
|
|
2674
|
+
}
|
|
2675
|
+
const baseDelay = isRateLimit ? 2e3 : initialDelay;
|
|
2676
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
2677
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
2678
|
+
} catch (error) {
|
|
2679
|
+
const errorMessage = error?.message || String(error);
|
|
2680
|
+
const isRateLimit = errorMessage?.includes("429") || errorMessage?.toLowerCase().includes("rate limit") || errorMessage?.toLowerCase().includes("too many requests");
|
|
2681
|
+
if (!isRateLimit || attempt === maxRetries) {
|
|
2682
|
+
throw error;
|
|
2683
|
+
}
|
|
2684
|
+
const baseDelay = isRateLimit ? 2e3 : initialDelay;
|
|
2685
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
2686
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
return null;
|
|
2690
|
+
}
|
|
2691
|
+
const preSwapQuotePromises = routers.map(async (router, index) => {
|
|
2692
|
+
const supportedChain = router.getSupportedChain();
|
|
2693
|
+
const isEvmRouter = supportedChain === "evm";
|
|
2694
|
+
let routeType;
|
|
2695
|
+
if (isEvmRouter) {
|
|
2696
|
+
routeType = "evm";
|
|
2697
|
+
} else {
|
|
2698
|
+
routeType = index === 0 ? "v1" : "v2";
|
|
2699
|
+
}
|
|
2700
|
+
const capabilities = router.getCapabilities();
|
|
2701
|
+
const quoteParams = capabilities.requiresRecipient ? {
|
|
2702
|
+
tokenIn: sourceToken,
|
|
2703
|
+
tokenOut: bluechipToken,
|
|
2704
|
+
amountIn,
|
|
2705
|
+
slippage,
|
|
2706
|
+
swapType: "EXACT_INPUT",
|
|
2707
|
+
sender: userAddress,
|
|
2708
|
+
recipient: userAddress
|
|
2709
|
+
} : {
|
|
2710
|
+
tokenIn: sourceToken,
|
|
2711
|
+
tokenOut: bluechipToken,
|
|
2712
|
+
amountIn,
|
|
2713
|
+
slippage,
|
|
2714
|
+
swapType: "EXACT_INPUT"
|
|
2715
|
+
};
|
|
2716
|
+
const routerName = router.getSupportedChain() === "evm" ? `EVM-${router.getChainId?.() || "unknown"}` : "NEAR";
|
|
2717
|
+
const routerType = router.okxAdapter ? "OKX" : router.bitgetAdapter ? "Bitget" : routerName;
|
|
2718
|
+
try {
|
|
2719
|
+
const preSwapQuote = await quoteWithRetry(
|
|
2720
|
+
router,
|
|
2721
|
+
quoteParams,
|
|
2722
|
+
routerType,
|
|
2723
|
+
routerName,
|
|
2724
|
+
2,
|
|
2725
|
+
1e3
|
|
2726
|
+
);
|
|
2727
|
+
if (!preSwapQuote || !preSwapQuote.success) {
|
|
2728
|
+
return null;
|
|
2729
|
+
}
|
|
2730
|
+
return {
|
|
2731
|
+
type: routeType,
|
|
2732
|
+
router,
|
|
2733
|
+
preSwapQuote
|
|
2734
|
+
};
|
|
2735
|
+
} catch (error) {
|
|
2736
|
+
return null;
|
|
2737
|
+
}
|
|
2738
|
+
});
|
|
2739
|
+
const preSwapQuoteResults = await Promise.allSettled(preSwapQuotePromises);
|
|
2740
|
+
const validPreSwapQuotes = [];
|
|
2741
|
+
preSwapQuoteResults.forEach((result) => {
|
|
2742
|
+
if (result.status === "fulfilled" && result.value !== null) {
|
|
2743
|
+
validPreSwapQuotes.push(result.value);
|
|
2744
|
+
}
|
|
2745
|
+
});
|
|
2746
|
+
let bestPreSwapQuote = null;
|
|
2747
|
+
if (validPreSwapQuotes.length > 0) {
|
|
2748
|
+
bestPreSwapQuote = validPreSwapQuotes.reduce((best, current) => {
|
|
2749
|
+
const bestAmount = new Big3(best.preSwapQuote.amountOut);
|
|
2750
|
+
const currentAmount = new Big3(current.preSwapQuote.amountOut);
|
|
2751
|
+
return currentAmount.gt(bestAmount) ? current : best;
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
const quotePaths = [];
|
|
2755
|
+
if (bestPreSwapQuote) {
|
|
2756
|
+
const { router, preSwapQuote, type: routeType } = bestPreSwapQuote;
|
|
2757
|
+
let normalizedSourceAsset;
|
|
2758
|
+
if (bluechipToken.assetId && (bluechipToken.assetId.startsWith("nep245:") || bluechipToken.assetId.startsWith("nep141:") || bluechipToken.assetId.startsWith("1cs_v1:"))) {
|
|
2759
|
+
normalizedSourceAsset = bluechipToken.assetId;
|
|
2760
|
+
} else if (isEvmChain) {
|
|
2761
|
+
const bluechipKey = bluechipToken.symbol?.toUpperCase();
|
|
2762
|
+
const bluechipTokenConfig = bluechipKey && bluechipTokens[bluechipKey] || void 0;
|
|
2763
|
+
normalizedSourceAsset = bluechipTokenConfig?.assetId ? bluechipTokenConfig.assetId : `evm:${normalizeEvmAddress(bluechipToken.address)}`;
|
|
2764
|
+
} else {
|
|
2765
|
+
const bluechipKey = bluechipToken.symbol?.toUpperCase() === "WNEAR" ? "NEAR" : bluechipToken.symbol?.toUpperCase();
|
|
2766
|
+
const bluechipTokenConfig = bluechipKey && bluechipTokens[bluechipKey] || void 0;
|
|
2767
|
+
normalizedSourceAsset = bluechipTokenConfig?.assetId ? bluechipTokenConfig.assetId : `nep141:${bluechipToken.address}`;
|
|
2768
|
+
}
|
|
2769
|
+
let normalizedTargetAsset;
|
|
2770
|
+
if (targetToken.assetId && (targetToken.assetId.startsWith("nep245:") || targetToken.assetId.startsWith("nep141:") || targetToken.assetId.startsWith("1cs_v1:"))) {
|
|
2771
|
+
normalizedTargetAsset = targetToken.assetId;
|
|
2772
|
+
} else {
|
|
2773
|
+
normalizedTargetAsset = targetToken.address;
|
|
2774
|
+
if (normalizedTargetAsset?.startsWith("1cs_v1:")) ; else if (normalizedTargetAsset && !normalizedTargetAsset.startsWith("nep141:") && !normalizedTargetAsset.startsWith("nep245:") && normalizedTargetAsset.includes(".")) {
|
|
2775
|
+
normalizedTargetAsset = `nep141:${normalizeTokenId(
|
|
2776
|
+
normalizedTargetAsset,
|
|
2777
|
+
wrapNearContractId
|
|
2778
|
+
)}`;
|
|
2779
|
+
}
|
|
2780
|
+
if (!normalizedTargetAsset?.startsWith("1cs_v1:")) {
|
|
2781
|
+
normalizedTargetAsset = normalizeDestinationAsset(normalizedTargetAsset, wrapNearContractId) || normalizedTargetAsset;
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
const slippageBps = convertSlippageToBasisPoints(slippage);
|
|
2785
|
+
const formattedAmountOut = preSwapQuote.amountOut;
|
|
2786
|
+
quotePaths.push({
|
|
2787
|
+
type: routeType,
|
|
2788
|
+
router,
|
|
2789
|
+
promise: (async () => {
|
|
2790
|
+
try {
|
|
2791
|
+
const intentsQuote = await intentsQuotationAdapter.quote({
|
|
2792
|
+
originAsset: normalizedSourceAsset,
|
|
2793
|
+
destinationAsset: normalizedTargetAsset,
|
|
2794
|
+
amount: formattedAmountOut,
|
|
2795
|
+
refundTo: refundTo || recipient,
|
|
2796
|
+
recipient,
|
|
2797
|
+
slippageTolerance: slippageBps,
|
|
2798
|
+
swapType: "FLEX_INPUT",
|
|
2799
|
+
...customRecipientMsg ? { customRecipientMsg } : {},
|
|
2800
|
+
...appFees ? { appFees } : {}
|
|
2801
|
+
});
|
|
2802
|
+
if (intentsQuote.quoteStatus !== "success") {
|
|
2803
|
+
const formatError = config.formatErrorMessage || formatErrorMessage;
|
|
2804
|
+
const errorMessage = formatError({
|
|
2805
|
+
error: intentsQuote.messageOriginal || intentsQuote.message,
|
|
2806
|
+
originAsset: normalizedSourceAsset,
|
|
2807
|
+
fallbackMessage: ErrorMessages.QUOTE_FAILED
|
|
2808
|
+
});
|
|
2809
|
+
throw new Error(errorMessage);
|
|
2810
|
+
}
|
|
2811
|
+
return {
|
|
2812
|
+
intentsQuote,
|
|
2813
|
+
preSwapQuote,
|
|
2814
|
+
router,
|
|
2815
|
+
finalAmountOut: intentsQuote.quoteSuccessResult?.quote?.amountOut || "0"
|
|
2816
|
+
};
|
|
2817
|
+
} catch (error) {
|
|
2818
|
+
throw error;
|
|
2819
|
+
}
|
|
2820
|
+
})()
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
if (isTokenIntentsSupported) {
|
|
2824
|
+
quotePaths.push({
|
|
2825
|
+
type: "intents",
|
|
2826
|
+
promise: (async () => {
|
|
2827
|
+
let normalizedSourceAsset;
|
|
2828
|
+
if (isEvmChain && sourceToken.platform === "nearIntents" && sourceToken.assetId) {
|
|
2829
|
+
normalizedSourceAsset = sourceToken.assetId;
|
|
2830
|
+
} else if (sourceToken.assetId && (sourceToken.assetId.startsWith("nep245:") || sourceToken.assetId.startsWith("nep141:") || sourceToken.assetId.startsWith("1cs_v1:"))) {
|
|
2831
|
+
normalizedSourceAsset = sourceToken.assetId;
|
|
2832
|
+
} else if (isEvmChain) {
|
|
2833
|
+
const sourceKey = sourceToken.symbol?.toUpperCase();
|
|
2834
|
+
const sourceTokenConfig = sourceKey ? bluechipTokens[sourceKey] : void 0;
|
|
2835
|
+
if (sourceTokenConfig?.assetId) {
|
|
2836
|
+
normalizedSourceAsset = sourceTokenConfig.assetId;
|
|
2837
|
+
} else if (sourceToken.address === "") {
|
|
2838
|
+
normalizedSourceAsset = `evm-${sourceChain}-native`;
|
|
2839
|
+
} else {
|
|
2840
|
+
normalizedSourceAsset = `evm:${normalizeEvmAddress(sourceToken.address)}`;
|
|
1989
2841
|
}
|
|
1990
2842
|
} else {
|
|
1991
2843
|
const sourceKey = sourceToken.symbol?.toUpperCase();
|
|
@@ -1999,15 +2851,22 @@ async function completeQuote(params, config) {
|
|
|
1999
2851
|
}
|
|
2000
2852
|
}
|
|
2001
2853
|
}
|
|
2002
|
-
let normalizedTargetAsset
|
|
2003
|
-
if (
|
|
2004
|
-
normalizedTargetAsset =
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2854
|
+
let normalizedTargetAsset;
|
|
2855
|
+
if (isEvmChain && targetToken.platform === "nearIntents" && targetToken.assetId) {
|
|
2856
|
+
normalizedTargetAsset = targetToken.assetId;
|
|
2857
|
+
} else if (targetToken.assetId && (targetToken.assetId.startsWith("nep245:") || targetToken.assetId.startsWith("nep141:") || targetToken.assetId.startsWith("1cs_v1:"))) {
|
|
2858
|
+
normalizedTargetAsset = targetToken.assetId;
|
|
2859
|
+
} else {
|
|
2860
|
+
normalizedTargetAsset = targetToken.address;
|
|
2861
|
+
if (normalizedTargetAsset?.startsWith("1cs_v1:")) ; else if (normalizedTargetAsset && !normalizedTargetAsset.startsWith("nep141:") && !normalizedTargetAsset.startsWith("nep245:") && normalizedTargetAsset.includes(".")) {
|
|
2862
|
+
normalizedTargetAsset = `nep141:${normalizeTokenId(
|
|
2863
|
+
normalizedTargetAsset,
|
|
2864
|
+
wrapNearContractId
|
|
2865
|
+
)}`;
|
|
2866
|
+
}
|
|
2867
|
+
if (!normalizedTargetAsset?.startsWith("1cs_v1:")) {
|
|
2868
|
+
normalizedTargetAsset = normalizeDestinationAsset(normalizedTargetAsset, wrapNearContractId) || normalizedTargetAsset;
|
|
2869
|
+
}
|
|
2011
2870
|
}
|
|
2012
2871
|
const slippageBps = convertSlippageToBasisPoints(slippage);
|
|
2013
2872
|
const intentsQuote = await intentsQuotationAdapter.quote({
|
|
@@ -2021,7 +2880,13 @@ async function completeQuote(params, config) {
|
|
|
2021
2880
|
...customRecipientMsg ? { customRecipientMsg } : {}
|
|
2022
2881
|
});
|
|
2023
2882
|
if (intentsQuote.quoteStatus !== "success") {
|
|
2024
|
-
|
|
2883
|
+
const formatError = config.formatErrorMessage || formatErrorMessage;
|
|
2884
|
+
const errorMessage = formatError({
|
|
2885
|
+
error: intentsQuote.messageOriginal || intentsQuote.message,
|
|
2886
|
+
originAsset: normalizedSourceAsset,
|
|
2887
|
+
fallbackMessage: ErrorMessages.QUOTE_FAILED
|
|
2888
|
+
});
|
|
2889
|
+
throw new Error(errorMessage);
|
|
2025
2890
|
}
|
|
2026
2891
|
return {
|
|
2027
2892
|
intentsQuote,
|
|
@@ -2034,6 +2899,7 @@ async function completeQuote(params, config) {
|
|
|
2034
2899
|
quotePaths.map((p) => p.promise)
|
|
2035
2900
|
);
|
|
2036
2901
|
const validPaths = [];
|
|
2902
|
+
const errorMessages = [];
|
|
2037
2903
|
pathResults.forEach((result, index) => {
|
|
2038
2904
|
const pathType = quotePaths[index].type;
|
|
2039
2905
|
if (result.status === "fulfilled") {
|
|
@@ -2041,10 +2907,28 @@ async function completeQuote(params, config) {
|
|
|
2041
2907
|
type: pathType,
|
|
2042
2908
|
...result.value
|
|
2043
2909
|
});
|
|
2910
|
+
} else if (result.status === "rejected") {
|
|
2911
|
+
const error = result.reason;
|
|
2912
|
+
if (error instanceof Error) {
|
|
2913
|
+
errorMessages.push(error.message);
|
|
2914
|
+
} else if (typeof error === "string") {
|
|
2915
|
+
errorMessages.push(error);
|
|
2916
|
+
} else {
|
|
2917
|
+
errorMessages.push(String(error));
|
|
2918
|
+
}
|
|
2044
2919
|
}
|
|
2045
2920
|
});
|
|
2046
2921
|
if (validPaths.length === 0) {
|
|
2047
|
-
|
|
2922
|
+
const intentsError = errorMessages.find(
|
|
2923
|
+
(msg) => msg && msg !== ErrorMessages.QUOTE_FAILED && (msg.toLowerCase().includes("bridge") || msg.toLowerCase().includes("amount") || msg.toLowerCase().includes("low") || msg.toLowerCase().includes("minimum"))
|
|
2924
|
+
);
|
|
2925
|
+
const bestError = intentsError || errorMessages[0] || ErrorMessages.QUOTE_FAILED;
|
|
2926
|
+
const formatError = config.formatErrorMessage || formatErrorMessage;
|
|
2927
|
+
const formattedError = formatError({
|
|
2928
|
+
error: bestError,
|
|
2929
|
+
fallbackMessage: ErrorMessages.QUOTE_FAILED
|
|
2930
|
+
});
|
|
2931
|
+
throw new Error(formattedError);
|
|
2048
2932
|
}
|
|
2049
2933
|
const bestPath = validPaths.reduce((best, current) => {
|
|
2050
2934
|
const bestAmount = new Big3(best.finalAmountOut);
|
|
@@ -2053,7 +2937,7 @@ async function completeQuote(params, config) {
|
|
|
2053
2937
|
});
|
|
2054
2938
|
const depositAddress = bestPath.intentsQuote.quoteSuccessResult?.quote?.depositAddress || "";
|
|
2055
2939
|
if (!depositAddress) {
|
|
2056
|
-
throw new Error(ErrorMessages.
|
|
2940
|
+
throw new Error(ErrorMessages.QUOTE_FAILED);
|
|
2057
2941
|
}
|
|
2058
2942
|
return {
|
|
2059
2943
|
intents: {
|
|
@@ -2104,28 +2988,32 @@ async function quoteSameChainSwap(params, dexRouters) {
|
|
|
2104
2988
|
return router.quote(quoteParams);
|
|
2105
2989
|
})
|
|
2106
2990
|
);
|
|
2107
|
-
const
|
|
2108
|
-
(r) => r.status === "fulfilled"
|
|
2991
|
+
const fulfilledResults = quoteResults.filter(
|
|
2992
|
+
(r) => r.status === "fulfilled"
|
|
2109
2993
|
).map((r, index) => ({
|
|
2110
|
-
|
|
2111
|
-
router: dexRouters[index]
|
|
2994
|
+
result: r.value,
|
|
2995
|
+
router: dexRouters[index],
|
|
2996
|
+
index
|
|
2112
2997
|
}));
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2998
|
+
const validQuotes = fulfilledResults.filter((r) => r.result.success).map((r) => ({
|
|
2999
|
+
quote: r.result,
|
|
3000
|
+
router: r.router
|
|
3001
|
+
}));
|
|
3002
|
+
if (validQuotes.length > 0) {
|
|
3003
|
+
return selectBestQuote(validQuotes);
|
|
3004
|
+
}
|
|
3005
|
+
const errors = fulfilledResults.filter((r) => !r.result.success).map((r) => {
|
|
3006
|
+
const error = r.result.error || "";
|
|
3007
|
+
const is429 = error.includes("429") || error.toLowerCase().includes("rate limit") || error.toLowerCase().includes("too many requests");
|
|
3008
|
+
return is429 ? null : `Router ${r.index}: ${error}`;
|
|
3009
|
+
}).filter(Boolean);
|
|
3010
|
+
if (errors.length === 0) {
|
|
3011
|
+
throw new Error("All liquidity providers are busy. Please try again later.");
|
|
3012
|
+
}
|
|
3013
|
+
const errorMessage = errors.length > 0 ? `${ErrorMessages.QUOTE_FAILED}: ${errors.join("; ")}` : ErrorMessages.QUOTE_FAILED;
|
|
3014
|
+
throw new Error(errorMessage);
|
|
2127
3015
|
}
|
|
2128
3016
|
|
|
2129
|
-
export { AggregateDexRouter, BitgetRouter, ErrorMessages, NearSmartRouter, completeQuote, convertSlippageToBasisPoints, findBestBluechipToken, findBestEvmBluechipToken, formatGasString, formatGasToTgas, getBluechipTokensConfig, getErrorMessage, isEvmIntentsSupportedToken, isNearIntentsSupportedToken, logger, normalizeDestinationAsset, normalizeError, normalizeEvmAddress, normalizeTokenId, quoteSameChainSwap, requiresRecipient, requiresRecipientInExecute, selectBestQuote, setBluechipTokensConfig };
|
|
3017
|
+
export { AggregateDexRouter, BitgetRouter, ErrorMessages, NearSmartRouter, OkxRouter, TRANSACTION_EXECUTION_ERROR_MESSAGE, completeQuote, convertSlippageToBasisPoints, findBestBluechipToken, findBestEvmBluechipToken, formatErrorMessage, formatGasString, formatGasToTgas, getBluechipTokensConfig, getErrorMessage, isEvmIntentsSupportedToken, isNearIntentsSupportedToken, logger, normalizeDestinationAsset, normalizeError, normalizeErrorForIntents, normalizeEvmAddress, normalizeTokenId, processErrorMessage, quoteSameChainSwap, requiresRecipient, requiresRecipientInExecute, selectBestQuote, setBluechipTokensConfig };
|
|
2130
3018
|
//# sourceMappingURL=index.mjs.map
|
|
2131
3019
|
//# sourceMappingURL=index.mjs.map
|