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