@rhea-finance/cross-chain-aggregation-dex 0.2.1 → 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 +189 -1
- package/dist/index.d.ts +189 -1
- package/dist/index.js +1100 -182
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1098 -183
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -49,14 +49,51 @@ function processErrorMessage(errorMessage) {
|
|
|
49
49
|
}
|
|
50
50
|
return TRANSACTION_EXECUTION_ERROR_MESSAGE;
|
|
51
51
|
}
|
|
52
|
-
function
|
|
53
|
-
if (!error) return ErrorMessages.
|
|
52
|
+
function normalizeErrorForIntents(error) {
|
|
53
|
+
if (!error) return ErrorMessages.QUOTE_FAILED;
|
|
54
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("
|
|
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")) {
|
|
56
56
|
return ErrorMessages.QUOTE_FAILED;
|
|
57
57
|
}
|
|
58
58
|
return processErrorMessage(error);
|
|
59
59
|
}
|
|
60
|
+
function normalizeError(error) {
|
|
61
|
+
if (!error) return ErrorMessages.EXECUTE_FAILED;
|
|
62
|
+
return error;
|
|
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
|
+
}
|
|
60
97
|
|
|
61
98
|
// src/utils/logger.ts
|
|
62
99
|
var LOG_LEVELS = {
|
|
@@ -420,7 +457,7 @@ var NearSmartRouter = class {
|
|
|
420
457
|
amountOut: "0",
|
|
421
458
|
minAmountOut: "0",
|
|
422
459
|
routes: [],
|
|
423
|
-
error:
|
|
460
|
+
error: response?.result_msg || response?.result_message || ErrorMessages.QUOTE_FAILED
|
|
424
461
|
};
|
|
425
462
|
}
|
|
426
463
|
const { routes: serverRoutes, amount_out } = response.result_data;
|
|
@@ -459,7 +496,7 @@ var NearSmartRouter = class {
|
|
|
459
496
|
amountOut: "0",
|
|
460
497
|
minAmountOut: "0",
|
|
461
498
|
routes: [],
|
|
462
|
-
error:
|
|
499
|
+
error: error?.message || ErrorMessages.QUOTE_FAILED
|
|
463
500
|
};
|
|
464
501
|
}
|
|
465
502
|
}
|
|
@@ -521,7 +558,6 @@ var NearSmartRouter = class {
|
|
|
521
558
|
},
|
|
522
559
|
gas: "50000000000000",
|
|
523
560
|
expandDeposit: "1250000000000000000000"
|
|
524
|
-
// 0.00125 NEAR
|
|
525
561
|
});
|
|
526
562
|
}
|
|
527
563
|
transactions.push({
|
|
@@ -577,7 +613,6 @@ var NearSmartRouter = class {
|
|
|
577
613
|
msg: JSON.stringify(swapMsg)
|
|
578
614
|
},
|
|
579
615
|
gas: "250",
|
|
580
|
-
// NEP-141 requires attaching 1 yoctoNEAR for certain calls.
|
|
581
616
|
expandDeposit: "1"
|
|
582
617
|
});
|
|
583
618
|
const result = await this.nearChainAdapter.call({
|
|
@@ -764,7 +799,7 @@ var AggregateDexRouter = class {
|
|
|
764
799
|
amountOut: "0",
|
|
765
800
|
minAmountOut: "0",
|
|
766
801
|
routes: [],
|
|
767
|
-
error:
|
|
802
|
+
error: error?.message || ErrorMessages.QUOTE_FAILED
|
|
768
803
|
};
|
|
769
804
|
}
|
|
770
805
|
}
|
|
@@ -896,7 +931,7 @@ var AggregateDexRouter = class {
|
|
|
896
931
|
} catch (error) {
|
|
897
932
|
return {
|
|
898
933
|
success: false,
|
|
899
|
-
error:
|
|
934
|
+
error: error?.message || ErrorMessages.QUOTE_FAILED
|
|
900
935
|
};
|
|
901
936
|
}
|
|
902
937
|
const routerMsg = finalQuote.routerMsg;
|
|
@@ -1099,7 +1134,8 @@ var AggregateDexRouter = class {
|
|
|
1099
1134
|
} catch (error) {
|
|
1100
1135
|
return {
|
|
1101
1136
|
success: false,
|
|
1102
|
-
|
|
1137
|
+
// ✅ 预交易阶段:保留原始错误信息,不简化
|
|
1138
|
+
error: error?.message || ErrorMessages.QUOTE_FAILED
|
|
1103
1139
|
};
|
|
1104
1140
|
}
|
|
1105
1141
|
const finalAmountToTransfer = finalQuoteForExecution.amountIn;
|
|
@@ -1267,17 +1303,9 @@ async function estimateGasLimit(gas, to, transactionData, value, sender, chainId
|
|
|
1267
1303
|
reason: estimateError?.reason,
|
|
1268
1304
|
message: estimateError?.message || String(estimateError)
|
|
1269
1305
|
};
|
|
1270
|
-
console.warn("RPC gas estimate failed:", {
|
|
1271
|
-
error: estimateError?.message || String(estimateError),
|
|
1272
|
-
code: estimateError?.code,
|
|
1273
|
-
reason: estimateError?.reason
|
|
1274
|
-
});
|
|
1275
1306
|
}
|
|
1276
1307
|
}
|
|
1277
1308
|
} catch (error) {
|
|
1278
|
-
console.warn("Gas estimation error:", {
|
|
1279
|
-
error: error?.message || String(error)
|
|
1280
|
-
});
|
|
1281
1309
|
}
|
|
1282
1310
|
if (estimatedGasLimit && estimatedGasLimit.gt(0) && hasReliableEstimate) {
|
|
1283
1311
|
return [estimatedGasLimit, true, rpcEstimateError];
|
|
@@ -1363,7 +1391,7 @@ var BitgetRouter = class {
|
|
|
1363
1391
|
amountOut: "0",
|
|
1364
1392
|
minAmountOut: "0",
|
|
1365
1393
|
routes: [],
|
|
1366
|
-
error:
|
|
1394
|
+
error: "Bitget quote failed: Router requires recipient address"
|
|
1367
1395
|
};
|
|
1368
1396
|
}
|
|
1369
1397
|
const { tokenIn, tokenOut, amountIn, slippage, sender, recipient } = params;
|
|
@@ -1376,7 +1404,7 @@ var BitgetRouter = class {
|
|
|
1376
1404
|
amountOut: "0",
|
|
1377
1405
|
minAmountOut: "0",
|
|
1378
1406
|
routes: [],
|
|
1379
|
-
error:
|
|
1407
|
+
error: "Bitget quote failed: Sender and recipient addresses are required"
|
|
1380
1408
|
};
|
|
1381
1409
|
}
|
|
1382
1410
|
if (tokenIn?.address === void 0 || tokenOut?.address === void 0) {
|
|
@@ -1388,7 +1416,7 @@ var BitgetRouter = class {
|
|
|
1388
1416
|
amountOut: "0",
|
|
1389
1417
|
minAmountOut: "0",
|
|
1390
1418
|
routes: [],
|
|
1391
|
-
error:
|
|
1419
|
+
error: "Bitget quote failed: Token addresses are required"
|
|
1392
1420
|
};
|
|
1393
1421
|
}
|
|
1394
1422
|
const normalizedTokenIn = tokenIn.address === "" ? "" : this.normalizeEvmAddress(tokenIn.address);
|
|
@@ -1410,7 +1438,7 @@ var BitgetRouter = class {
|
|
|
1410
1438
|
if (!isBitgetResponseSuccess(response) || !response.data) {
|
|
1411
1439
|
return createQuoteError(
|
|
1412
1440
|
params,
|
|
1413
|
-
|
|
1441
|
+
response.msg || "Bitget API error: Failed to get quote"
|
|
1414
1442
|
);
|
|
1415
1443
|
}
|
|
1416
1444
|
const {
|
|
@@ -1476,21 +1504,23 @@ var BitgetRouter = class {
|
|
|
1476
1504
|
} catch (error) {
|
|
1477
1505
|
return createQuoteError(
|
|
1478
1506
|
params,
|
|
1479
|
-
|
|
1507
|
+
error?.message || "Bitget quote failed: Unknown error"
|
|
1480
1508
|
);
|
|
1481
1509
|
}
|
|
1482
1510
|
}
|
|
1483
1511
|
async executeSwap(params) {
|
|
1484
1512
|
try {
|
|
1485
1513
|
if (!requiresRecipientInExecute(params)) {
|
|
1486
|
-
return createExecuteError(
|
|
1514
|
+
return createExecuteError("Bitget swap failed: Router requires recipient address");
|
|
1487
1515
|
}
|
|
1488
1516
|
const { quote, sender, receiveUser } = params;
|
|
1489
1517
|
if (!quote.success) {
|
|
1490
|
-
return createExecuteError(
|
|
1518
|
+
return createExecuteError(
|
|
1519
|
+
quote.error || "Bitget swap failed: Invalid quote"
|
|
1520
|
+
);
|
|
1491
1521
|
}
|
|
1492
1522
|
if (!receiveUser || receiveUser.trim() === "") {
|
|
1493
|
-
return createExecuteError(
|
|
1523
|
+
return createExecuteError("Bitget swap failed: Recipient address is required");
|
|
1494
1524
|
}
|
|
1495
1525
|
let market;
|
|
1496
1526
|
try {
|
|
@@ -1498,13 +1528,13 @@ var BitgetRouter = class {
|
|
|
1498
1528
|
const routerMsg = JSON.parse(quote.routerMsg);
|
|
1499
1529
|
market = routerMsg.market || "";
|
|
1500
1530
|
} else {
|
|
1501
|
-
return createExecuteError(
|
|
1531
|
+
return createExecuteError("Bitget swap failed: Missing router message data");
|
|
1502
1532
|
}
|
|
1503
1533
|
} catch (error) {
|
|
1504
|
-
return createExecuteError(
|
|
1534
|
+
return createExecuteError("Bitget swap failed: Invalid router message format");
|
|
1505
1535
|
}
|
|
1506
1536
|
if (!market) {
|
|
1507
|
-
return createExecuteError(
|
|
1537
|
+
return createExecuteError("Bitget swap failed: Market information is required");
|
|
1508
1538
|
}
|
|
1509
1539
|
const normalizedTokenIn = this.normalizeEvmAddress(
|
|
1510
1540
|
quote.tokenIn.address
|
|
@@ -1527,9 +1557,8 @@ var BitgetRouter = class {
|
|
|
1527
1557
|
tokenOutDecimals: quote.tokenOut.decimals
|
|
1528
1558
|
});
|
|
1529
1559
|
if (!isBitgetResponseSuccess(reQuoteResponse) || !reQuoteResponse.data) {
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
);
|
|
1560
|
+
const errorMsg = reQuoteResponse.msg || "Bitget re-quote failed: No data returned";
|
|
1561
|
+
return createExecuteError(errorMsg);
|
|
1533
1562
|
}
|
|
1534
1563
|
market = reQuoteResponse.data?.market || market;
|
|
1535
1564
|
const swapResponse = await this.bitgetAdapter.swap({
|
|
@@ -1548,11 +1577,13 @@ var BitgetRouter = class {
|
|
|
1548
1577
|
});
|
|
1549
1578
|
if (!isBitgetResponseSuccess(swapResponse) || !swapResponse.data) {
|
|
1550
1579
|
return createExecuteError(
|
|
1551
|
-
|
|
1580
|
+
swapResponse.msg || "Bitget swap failed: No transaction data"
|
|
1552
1581
|
);
|
|
1553
1582
|
}
|
|
1554
1583
|
if (swapResponse.data?.estimateRevert === true) {
|
|
1555
|
-
return createExecuteError(
|
|
1584
|
+
return createExecuteError(
|
|
1585
|
+
swapResponse.msg || "Bitget swap failed: Transaction would revert (slippage or price impact too high)"
|
|
1586
|
+
);
|
|
1556
1587
|
}
|
|
1557
1588
|
let transactionData = swapResponse.data?.calldata || swapResponse.data?.data;
|
|
1558
1589
|
const to = swapResponse.data?.contract || swapResponse.data?.to;
|
|
@@ -1566,14 +1597,18 @@ var BitgetRouter = class {
|
|
|
1566
1597
|
}
|
|
1567
1598
|
const gas = swapResponse.data?.gas || (swapResponse.data?.computeUnits !== void 0 ? String(swapResponse.data.computeUnits) : void 0);
|
|
1568
1599
|
if (!to || !transactionData) {
|
|
1569
|
-
return createExecuteError(
|
|
1600
|
+
return createExecuteError(
|
|
1601
|
+
swapResponse.msg || "Bitget swap failed: Missing transaction data or recipient address"
|
|
1602
|
+
);
|
|
1570
1603
|
}
|
|
1571
1604
|
if (transactionData.length < 10 || !/^0x[0-9a-fA-F]+$/.test(transactionData)) {
|
|
1572
|
-
return createExecuteError(
|
|
1605
|
+
return createExecuteError(
|
|
1606
|
+
swapResponse.msg || "Bitget swap failed: Invalid transaction data format"
|
|
1607
|
+
);
|
|
1573
1608
|
}
|
|
1574
1609
|
if (normalizedTokenIn && normalizedTokenIn !== "") {
|
|
1575
1610
|
if (!this.evmChainAdapter.getBalance) {
|
|
1576
|
-
return createExecuteError(
|
|
1611
|
+
return createExecuteError("Bitget swap failed: Balance check not supported");
|
|
1577
1612
|
}
|
|
1578
1613
|
const tokenBalanceFormatted = await this.evmChainAdapter.getBalance({
|
|
1579
1614
|
address: sender,
|
|
@@ -1583,7 +1618,7 @@ var BitgetRouter = class {
|
|
|
1583
1618
|
const balanceBN = ethers.ethers.utils.parseUnits(tokenBalanceFormatted || "0", tokenDecimals);
|
|
1584
1619
|
const amountInBN = ethers.ethers.BigNumber.from(quote.amountIn);
|
|
1585
1620
|
if (balanceBN.lt(amountInBN)) {
|
|
1586
|
-
return createExecuteError(
|
|
1621
|
+
return createExecuteError("Bitget swap failed: Insufficient token balance");
|
|
1587
1622
|
}
|
|
1588
1623
|
const currentAllowance = await this.evmChainAdapter.getAllowance({
|
|
1589
1624
|
tokenAddress: normalizedTokenIn,
|
|
@@ -1616,17 +1651,17 @@ var BitgetRouter = class {
|
|
|
1616
1651
|
retryCount++;
|
|
1617
1652
|
}
|
|
1618
1653
|
if (newAllowanceBN.lt(amountInBN)) {
|
|
1619
|
-
return createExecuteError(
|
|
1654
|
+
return createExecuteError("Bitget swap failed: Token approval failed or insufficient");
|
|
1620
1655
|
}
|
|
1621
1656
|
} catch (error) {
|
|
1622
1657
|
return createExecuteError(
|
|
1623
|
-
|
|
1658
|
+
error?.message || "Bitget approval check failed"
|
|
1624
1659
|
);
|
|
1625
1660
|
}
|
|
1626
1661
|
}
|
|
1627
1662
|
} else {
|
|
1628
1663
|
if (!this.evmChainAdapter.getBalance) {
|
|
1629
|
-
return createExecuteError(
|
|
1664
|
+
return createExecuteError("Bitget swap failed: Balance check not supported");
|
|
1630
1665
|
}
|
|
1631
1666
|
const nativeBalanceFormatted = await this.evmChainAdapter.getBalance({
|
|
1632
1667
|
address: sender,
|
|
@@ -1650,7 +1685,7 @@ var BitgetRouter = class {
|
|
|
1650
1685
|
const gasCostEstimate = estimatedGasLimitForBalance.mul(gasPriceEstimate2);
|
|
1651
1686
|
const totalRequired = amountInBN.add(gasCostEstimate);
|
|
1652
1687
|
if (balanceBN.lt(totalRequired)) {
|
|
1653
|
-
return createExecuteError(
|
|
1688
|
+
return createExecuteError("Bitget swap failed: Insufficient balance (including gas fee)");
|
|
1654
1689
|
}
|
|
1655
1690
|
}
|
|
1656
1691
|
if (normalizedTokenIn && normalizedTokenIn !== "") {
|
|
@@ -1662,7 +1697,7 @@ var BitgetRouter = class {
|
|
|
1662
1697
|
const finalAllowanceBN = ethers.ethers.BigNumber.from(finalAllowanceCheck);
|
|
1663
1698
|
const amountInBN = ethers.ethers.BigNumber.from(quote.amountIn);
|
|
1664
1699
|
if (finalAllowanceBN.lt(amountInBN)) {
|
|
1665
|
-
return createExecuteError(
|
|
1700
|
+
return createExecuteError("Bitget swap failed: Insufficient token allowance");
|
|
1666
1701
|
}
|
|
1667
1702
|
}
|
|
1668
1703
|
const [estimatedGasLimit, hasReliableEstimate, rpcEstimateError] = await estimateGasLimit(
|
|
@@ -1683,10 +1718,13 @@ var BitgetRouter = class {
|
|
|
1683
1718
|
tokenOut: quote.tokenOut.symbol,
|
|
1684
1719
|
rpcError: rpcEstimateError
|
|
1685
1720
|
});
|
|
1686
|
-
|
|
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
|
+
);
|
|
1687
1725
|
}
|
|
1688
1726
|
if (!gas || gas === "0") {
|
|
1689
|
-
return createExecuteError(
|
|
1727
|
+
return createExecuteError("Bitget swap failed: Invalid gas estimate from Bitget");
|
|
1690
1728
|
}
|
|
1691
1729
|
logger.warn("RPC gas estimation failed, using Bitget estimate", {
|
|
1692
1730
|
chainId: this.chainId,
|
|
@@ -1719,7 +1757,7 @@ var BitgetRouter = class {
|
|
|
1719
1757
|
if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas || feeData.maxFeePerGas.lte(0) || feeData.maxPriorityFeePerGas.lte(0)) {
|
|
1720
1758
|
const cachedGasPrice = await getCachedGasPrice();
|
|
1721
1759
|
if (!cachedGasPrice || cachedGasPrice.lte(0)) {
|
|
1722
|
-
return createExecuteError(
|
|
1760
|
+
return createExecuteError("Bitget swap failed: Unable to get valid gas price");
|
|
1723
1761
|
}
|
|
1724
1762
|
feeData.maxFeePerGas = cachedGasPrice;
|
|
1725
1763
|
feeData.maxPriorityFeePerGas = cachedGasPrice.div(10);
|
|
@@ -1752,27 +1790,27 @@ var BitgetRouter = class {
|
|
|
1752
1790
|
} catch (error) {
|
|
1753
1791
|
const cachedGasPrice = await getCachedGasPrice();
|
|
1754
1792
|
if (!cachedGasPrice || cachedGasPrice.lte(0)) {
|
|
1755
|
-
return createExecuteError(
|
|
1793
|
+
return createExecuteError("Bitget swap failed: Unable to get valid gas price");
|
|
1756
1794
|
}
|
|
1757
1795
|
feeData.gasPrice = cachedGasPrice;
|
|
1758
1796
|
}
|
|
1759
1797
|
}
|
|
1760
1798
|
if (!to || !ethers.ethers.utils.isAddress(to)) {
|
|
1761
|
-
return createExecuteError(
|
|
1799
|
+
return createExecuteError("Bitget swap failed: Invalid recipient address");
|
|
1762
1800
|
}
|
|
1763
1801
|
if (!transactionData || transactionData.length < 10) {
|
|
1764
|
-
return createExecuteError(
|
|
1802
|
+
return createExecuteError("Bitget swap failed: Invalid transaction data");
|
|
1765
1803
|
}
|
|
1766
1804
|
if (!estimatedGasLimit || estimatedGasLimit.lte(0)) {
|
|
1767
|
-
return createExecuteError(
|
|
1805
|
+
return createExecuteError("Bitget swap failed: Invalid gas limit");
|
|
1768
1806
|
}
|
|
1769
1807
|
if (supportsEip1559) {
|
|
1770
1808
|
if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas || feeData.maxFeePerGas.lte(0) || feeData.maxPriorityFeePerGas.lte(0)) {
|
|
1771
|
-
return createExecuteError(
|
|
1809
|
+
return createExecuteError("Bitget swap failed: Invalid gas fee data (EIP-1559)");
|
|
1772
1810
|
}
|
|
1773
1811
|
} else {
|
|
1774
1812
|
if (!feeData.gasPrice || feeData.gasPrice.lte(0)) {
|
|
1775
|
-
return createExecuteError(
|
|
1813
|
+
return createExecuteError("Bitget swap failed: Invalid gas price");
|
|
1776
1814
|
}
|
|
1777
1815
|
}
|
|
1778
1816
|
const txParams = {
|
|
@@ -1812,118 +1850,949 @@ var BitgetRouter = class {
|
|
|
1812
1850
|
}
|
|
1813
1851
|
}
|
|
1814
1852
|
};
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
targetChain: _targetChain,
|
|
1821
|
-
// Reserved for future use
|
|
1822
|
-
amountIn,
|
|
1823
|
-
slippage,
|
|
1824
|
-
recipient,
|
|
1825
|
-
refundTo,
|
|
1826
|
-
customRecipientMsg,
|
|
1827
|
-
appFees,
|
|
1828
|
-
evmChainId
|
|
1829
|
-
} = params;
|
|
1830
|
-
const {
|
|
1831
|
-
intentsQuotationAdapter,
|
|
1832
|
-
dexRouters,
|
|
1833
|
-
dexRouter,
|
|
1834
|
-
bluechipTokens,
|
|
1835
|
-
configAdapter,
|
|
1836
|
-
currentUserAddress,
|
|
1837
|
-
isIntentsSupportedToken: customIsIntentsSupportedToken
|
|
1838
|
-
} = config;
|
|
1839
|
-
const wrapNearContractId = configAdapter.getWrapNearContractId();
|
|
1840
|
-
const routers = dexRouters || (dexRouter ? [dexRouter] : []);
|
|
1841
|
-
const userAddress = currentUserAddress || recipient;
|
|
1842
|
-
if (!userAddress) {
|
|
1843
|
-
throw new Error(ErrorMessages.QUOTE_FAILED);
|
|
1853
|
+
var OkxRouter = class {
|
|
1854
|
+
constructor(config) {
|
|
1855
|
+
this.okxAdapter = config.okxAdapter;
|
|
1856
|
+
this.evmChainAdapter = config.evmChainAdapter;
|
|
1857
|
+
this.chainId = config.chainId;
|
|
1844
1858
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1859
|
+
getCapabilities() {
|
|
1860
|
+
return {
|
|
1861
|
+
requiresRecipient: true,
|
|
1862
|
+
requiresFinalizeQuote: false,
|
|
1863
|
+
requiresComplexRegistration: false,
|
|
1864
|
+
supportedChain: "evm"
|
|
1865
|
+
};
|
|
1847
1866
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1867
|
+
getSupportedChain() {
|
|
1868
|
+
return "evm";
|
|
1850
1869
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
const bluechipToken = isEvmChain ? findBestEvmBluechipToken(
|
|
1854
|
-
bluechipTokens,
|
|
1855
|
-
configAdapter.getEvmNativeWrappedTokenAddress?.()
|
|
1856
|
-
) : findBestBluechipToken(bluechipTokens, wrapNearContractId);
|
|
1857
|
-
if (!bluechipToken?.address) {
|
|
1858
|
-
throw new Error(ErrorMessages.QUOTE_FAILED);
|
|
1870
|
+
getChainId() {
|
|
1871
|
+
return this.chainId;
|
|
1859
1872
|
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
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);
|
|
1869
1986
|
}
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
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
|
+
});
|
|
1893
2040
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
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
|
|
2055
|
+
});
|
|
1903
2056
|
}
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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;
|
|
1910
2110
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
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");
|
|
1913
2170
|
}
|
|
1914
|
-
const
|
|
1915
|
-
|
|
1916
|
-
|
|
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
|
+
}
|
|
1917
2247
|
try {
|
|
1918
|
-
const
|
|
1919
|
-
|
|
1920
|
-
|
|
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");
|
|
1921
2342
|
}
|
|
1922
|
-
formattedAmountOut = amountBN.toFixed(0, Big3__default.default.roundDown);
|
|
1923
2343
|
} catch (error) {
|
|
1924
|
-
|
|
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;
|
|
1925
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;
|
|
1926
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 () => {
|
|
1927
2796
|
try {
|
|
1928
2797
|
const intentsQuote = await intentsQuotationAdapter.quote({
|
|
1929
2798
|
originAsset: normalizedSourceAsset,
|
|
@@ -1937,7 +2806,13 @@ async function completeQuote(params, config) {
|
|
|
1937
2806
|
...appFees ? { appFees } : {}
|
|
1938
2807
|
});
|
|
1939
2808
|
if (intentsQuote.quoteStatus !== "success") {
|
|
1940
|
-
|
|
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);
|
|
1941
2816
|
}
|
|
1942
2817
|
return {
|
|
1943
2818
|
intentsQuote,
|
|
@@ -1950,13 +2825,17 @@ async function completeQuote(params, config) {
|
|
|
1950
2825
|
}
|
|
1951
2826
|
})()
|
|
1952
2827
|
});
|
|
1953
|
-
}
|
|
2828
|
+
}
|
|
1954
2829
|
if (isTokenIntentsSupported) {
|
|
1955
2830
|
quotePaths.push({
|
|
1956
2831
|
type: "intents",
|
|
1957
2832
|
promise: (async () => {
|
|
1958
2833
|
let normalizedSourceAsset;
|
|
1959
|
-
if (isEvmChain) {
|
|
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) {
|
|
1960
2839
|
const sourceKey = sourceToken.symbol?.toUpperCase();
|
|
1961
2840
|
const sourceTokenConfig = sourceKey ? bluechipTokens[sourceKey] : void 0;
|
|
1962
2841
|
if (sourceTokenConfig?.assetId) {
|
|
@@ -1978,15 +2857,22 @@ async function completeQuote(params, config) {
|
|
|
1978
2857
|
}
|
|
1979
2858
|
}
|
|
1980
2859
|
}
|
|
1981
|
-
let normalizedTargetAsset
|
|
1982
|
-
if (
|
|
1983
|
-
normalizedTargetAsset =
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
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
|
+
}
|
|
1990
2876
|
}
|
|
1991
2877
|
const slippageBps = convertSlippageToBasisPoints(slippage);
|
|
1992
2878
|
const intentsQuote = await intentsQuotationAdapter.quote({
|
|
@@ -2000,7 +2886,13 @@ async function completeQuote(params, config) {
|
|
|
2000
2886
|
...customRecipientMsg ? { customRecipientMsg } : {}
|
|
2001
2887
|
});
|
|
2002
2888
|
if (intentsQuote.quoteStatus !== "success") {
|
|
2003
|
-
|
|
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);
|
|
2004
2896
|
}
|
|
2005
2897
|
return {
|
|
2006
2898
|
intentsQuote,
|
|
@@ -2013,6 +2905,7 @@ async function completeQuote(params, config) {
|
|
|
2013
2905
|
quotePaths.map((p) => p.promise)
|
|
2014
2906
|
);
|
|
2015
2907
|
const validPaths = [];
|
|
2908
|
+
const errorMessages = [];
|
|
2016
2909
|
pathResults.forEach((result, index) => {
|
|
2017
2910
|
const pathType = quotePaths[index].type;
|
|
2018
2911
|
if (result.status === "fulfilled") {
|
|
@@ -2020,10 +2913,28 @@ async function completeQuote(params, config) {
|
|
|
2020
2913
|
type: pathType,
|
|
2021
2914
|
...result.value
|
|
2022
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
|
+
}
|
|
2023
2925
|
}
|
|
2024
2926
|
});
|
|
2025
2927
|
if (validPaths.length === 0) {
|
|
2026
|
-
|
|
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);
|
|
2027
2938
|
}
|
|
2028
2939
|
const bestPath = validPaths.reduce((best, current) => {
|
|
2029
2940
|
const bestAmount = new Big3__default.default(best.finalAmountOut);
|
|
@@ -2083,37 +2994,43 @@ async function quoteSameChainSwap(params, dexRouters) {
|
|
|
2083
2994
|
return router.quote(quoteParams);
|
|
2084
2995
|
})
|
|
2085
2996
|
);
|
|
2086
|
-
const
|
|
2087
|
-
(r) => r.status === "fulfilled"
|
|
2997
|
+
const fulfilledResults = quoteResults.filter(
|
|
2998
|
+
(r) => r.status === "fulfilled"
|
|
2088
2999
|
).map((r, index) => ({
|
|
2089
|
-
|
|
2090
|
-
router: dexRouters[index]
|
|
3000
|
+
result: r.value,
|
|
3001
|
+
router: dexRouters[index],
|
|
3002
|
+
index
|
|
2091
3003
|
}));
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
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);
|
|
2106
3021
|
}
|
|
2107
3022
|
|
|
2108
3023
|
exports.AggregateDexRouter = AggregateDexRouter;
|
|
2109
3024
|
exports.BitgetRouter = BitgetRouter;
|
|
2110
3025
|
exports.ErrorMessages = ErrorMessages;
|
|
2111
3026
|
exports.NearSmartRouter = NearSmartRouter;
|
|
3027
|
+
exports.OkxRouter = OkxRouter;
|
|
2112
3028
|
exports.TRANSACTION_EXECUTION_ERROR_MESSAGE = TRANSACTION_EXECUTION_ERROR_MESSAGE;
|
|
2113
3029
|
exports.completeQuote = completeQuote;
|
|
2114
3030
|
exports.convertSlippageToBasisPoints = convertSlippageToBasisPoints;
|
|
2115
3031
|
exports.findBestBluechipToken = findBestBluechipToken;
|
|
2116
3032
|
exports.findBestEvmBluechipToken = findBestEvmBluechipToken;
|
|
3033
|
+
exports.formatErrorMessage = formatErrorMessage;
|
|
2117
3034
|
exports.formatGasString = formatGasString;
|
|
2118
3035
|
exports.formatGasToTgas = formatGasToTgas;
|
|
2119
3036
|
exports.getBluechipTokensConfig = getBluechipTokensConfig;
|
|
@@ -2123,6 +3040,7 @@ exports.isNearIntentsSupportedToken = isNearIntentsSupportedToken;
|
|
|
2123
3040
|
exports.logger = logger;
|
|
2124
3041
|
exports.normalizeDestinationAsset = normalizeDestinationAsset;
|
|
2125
3042
|
exports.normalizeError = normalizeError;
|
|
3043
|
+
exports.normalizeErrorForIntents = normalizeErrorForIntents;
|
|
2126
3044
|
exports.normalizeEvmAddress = normalizeEvmAddress;
|
|
2127
3045
|
exports.normalizeTokenId = normalizeTokenId;
|
|
2128
3046
|
exports.processErrorMessage = processErrorMessage;
|