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