four-flap-meme-sdk 1.5.92 → 1.5.94
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/xlayer/bundle.js +132 -40
- package/dist/xlayer/constants.d.ts +1 -1
- package/dist/xlayer/constants.js +2 -1
- package/dist/xlayer/dex-bundle-swap.d.ts +12 -0
- package/dist/xlayer/dex-bundle-swap.js +106 -18
- package/dist/xlayer/portal-ops.d.ts +5 -1
- package/dist/xlayer/portal-ops.js +6 -2
- package/dist/xlayer/types.d.ts +4 -0
- package/package.json +1 -1
package/dist/xlayer/bundle.js
CHANGED
|
@@ -14,7 +14,7 @@ import { encodeBuyCall, encodeBuyCallV3, encodeSellCall, encodeApproveCall, enco
|
|
|
14
14
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
15
15
|
import { PROFIT_CONFIG, ZERO_ADDRESS } from '../utils/constants.js';
|
|
16
16
|
import { DexQuery } from './dex.js';
|
|
17
|
-
import { encodeSwapExactETHForTokensSupportingFee, encodeSwapExactETHForTokensV3, } from './dex.js';
|
|
17
|
+
import { encodeSwapExactETHForTokensSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactTokensForETHV3, } from './dex.js';
|
|
18
18
|
// ============================================================================
|
|
19
19
|
// AA Nonce(EntryPoint nonce)本地分配器
|
|
20
20
|
// ============================================================================
|
|
@@ -557,6 +557,10 @@ export class BundleExecutor {
|
|
|
557
557
|
if (profitSettings.extractProfit && nativeProfitAmount > 0n) {
|
|
558
558
|
console.log(`[利润提取] 总利润: ${useNativeToken ? formatOkb(nativeProfitAmount) : `${formatOkb(totalProfitWei)} (ERC20) -> ${formatOkb(nativeProfitAmount)} (OKB)`} -> ${profitSettings.profitRecipient}`);
|
|
559
559
|
}
|
|
560
|
+
// ✅ 获取代币状态,判断是走内盘 Portal 还是外盘 DEX
|
|
561
|
+
const tokenState = await this.portalQuery.getTokenV7(tokenAddress);
|
|
562
|
+
const isGraduated = tokenState.status === 4; // DEX = 4 表示已毕业
|
|
563
|
+
console.log(`[BundleBuy] Token status: ${tokenState.status} (${isGraduated ? 'DEX/已毕业' : '内盘'})`);
|
|
560
564
|
// 1. 预取账户信息(并行),并批量估算 gas(减少对 bundler 的 N 次请求)
|
|
561
565
|
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
562
566
|
const nonceMap = new AANonceMap();
|
|
@@ -575,30 +579,62 @@ export class BundleExecutor {
|
|
|
575
579
|
// TODO: 如果需要,可以在这里检查并转账 ERC20 代币到 sender
|
|
576
580
|
}
|
|
577
581
|
});
|
|
578
|
-
// ✅ 构建买入 callData
|
|
579
|
-
const buyCallDatas = buyWeis.map((buyWei) => {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
582
|
+
// ✅ 构建买入 callData:根据代币状态选择 Portal(内盘)或 DEX(外盘)
|
|
583
|
+
const buyCallDatas = buyWeis.map((buyWei, i) => {
|
|
584
|
+
if (isGraduated) {
|
|
585
|
+
// ✅ 已毕业:使用 DEX Router 买入
|
|
586
|
+
const deadline = Math.floor(Date.now() / 1000) + 1200; // 20 min
|
|
587
|
+
const dexType = (tokenState.lpFeeProfile !== undefined && tokenState.lpFeeProfile >= 0) ? 'V3' : 'V2';
|
|
588
|
+
const recipient = accountInfos[i].sender;
|
|
589
|
+
let swapData;
|
|
590
|
+
let routerAddress;
|
|
591
|
+
if (dexType === 'V3') {
|
|
592
|
+
routerAddress = POTATOSWAP_V3_ROUTER;
|
|
593
|
+
const v3Fee = lpFeeProfileToV3Fee(tokenState.lpFeeProfile);
|
|
594
|
+
swapData = encodeSwapExactETHForTokensV3({
|
|
595
|
+
tokenIn: WOKB,
|
|
596
|
+
tokenOut: tokenAddress,
|
|
597
|
+
fee: v3Fee,
|
|
598
|
+
recipient,
|
|
599
|
+
deadline,
|
|
600
|
+
amountIn: buyWei,
|
|
601
|
+
amountOutMinimum: 0n,
|
|
602
|
+
sqrtPriceLimitX96: 0n
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
routerAddress = POTATOSWAP_V2_ROUTER;
|
|
607
|
+
swapData = encodeSwapExactETHForTokensSupportingFee(0n, [WOKB, tokenAddress], recipient, deadline);
|
|
608
|
+
}
|
|
609
|
+
if (i < 3) {
|
|
610
|
+
console.log(`[BundleBuy] Wallet[${i}]: 使用 ${dexType} DEX Router 买入, fee=${dexType === 'V3' ? lpFeeProfileToV3Fee(tokenState.lpFeeProfile) : 'N/A'}`);
|
|
611
|
+
}
|
|
612
|
+
return encodeExecute(routerAddress, buyWei, swapData);
|
|
597
613
|
}
|
|
598
614
|
else {
|
|
599
|
-
//
|
|
600
|
-
const
|
|
601
|
-
|
|
615
|
+
// ✅ 未毕业:使用 Portal 的 swapExactInput
|
|
616
|
+
const portalIface = new Interface([
|
|
617
|
+
'function swapExactInput((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData) params) payable returns (uint256)',
|
|
618
|
+
]);
|
|
619
|
+
const swapData = portalIface.encodeFunctionData('swapExactInput', [
|
|
620
|
+
{
|
|
621
|
+
inputToken,
|
|
622
|
+
outputToken: tokenAddress,
|
|
623
|
+
inputAmount: buyWei,
|
|
624
|
+
minOutputAmount: 0n,
|
|
625
|
+
permitData: '0x',
|
|
626
|
+
},
|
|
627
|
+
]);
|
|
628
|
+
// ✅ ERC20 代币需要先 approve,使用 executeBatch 将 approve + swap 合并为一个 UserOp
|
|
629
|
+
if (useNativeToken) {
|
|
630
|
+
// 原生代币:直接 swap
|
|
631
|
+
return encodeExecute(this.portalAddress, buyWei, swapData);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
// ERC20 代币:approve + swap 批量调用
|
|
635
|
+
const approveData = encodeApproveCall(this.portalAddress, buyWei);
|
|
636
|
+
return encodeExecuteBatch([inputToken, this.portalAddress], [0n, 0n], [approveData, swapData]);
|
|
637
|
+
}
|
|
602
638
|
}
|
|
603
639
|
});
|
|
604
640
|
const initCodes = accountInfos.map((ai, i) => ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address));
|
|
@@ -730,6 +766,10 @@ export class BundleExecutor {
|
|
|
730
766
|
console.log('token:', tokenAddress);
|
|
731
767
|
console.log('owners:', wallets.length);
|
|
732
768
|
console.log('sellPercent:', sellPercent);
|
|
769
|
+
// ✅ 获取代币状态,判断是走内盘 Portal 还是外盘 DEX
|
|
770
|
+
const tokenState = await this.portalQuery.getTokenV7(tokenAddress);
|
|
771
|
+
const isGraduated = tokenState.status === 4; // DEX = 4 表示已毕业
|
|
772
|
+
console.log(`[BundleSell] Token status: ${tokenState.status} (${isGraduated ? 'DEX/已毕业' : '内盘'})`);
|
|
733
773
|
// ✅ 批量获取 accountInfo(含 sender/nonce/deployed),避免循环内重复 getAccountInfo
|
|
734
774
|
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
735
775
|
const senders = accountInfos.map((ai) => ai.sender);
|
|
@@ -757,18 +797,71 @@ export class BundleExecutor {
|
|
|
757
797
|
touched[i] = true;
|
|
758
798
|
}
|
|
759
799
|
if (sellItems.length > 0) {
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
800
|
+
const effConfig = { ...(this.config ?? {}), ...(config ?? {}) };
|
|
801
|
+
if (isGraduated) {
|
|
802
|
+
// ✅ 已毕业:使用 DEX Router 卖出
|
|
803
|
+
const deadline = Math.floor(Date.now() / 1000) + 1200; // 20 min
|
|
804
|
+
const dexType = (tokenState.lpFeeProfile !== undefined && tokenState.lpFeeProfile >= 0) ? 'V3' : 'V2';
|
|
805
|
+
const signedSells = await mapWithConcurrency(sellItems, 4, async (it) => {
|
|
806
|
+
const i = it.i;
|
|
807
|
+
let swapData;
|
|
808
|
+
let routerAddress;
|
|
809
|
+
if (dexType === 'V3') {
|
|
810
|
+
routerAddress = POTATOSWAP_V3_ROUTER;
|
|
811
|
+
const v3Fee = lpFeeProfileToV3Fee(tokenState.lpFeeProfile);
|
|
812
|
+
swapData = encodeSwapExactTokensForETHV3({
|
|
813
|
+
tokenIn: tokenAddress,
|
|
814
|
+
tokenOut: WOKB,
|
|
815
|
+
fee: v3Fee,
|
|
816
|
+
recipient: it.sender,
|
|
817
|
+
deadline,
|
|
818
|
+
amountIn: it.sellAmount,
|
|
819
|
+
amountOutMinimum: 0n,
|
|
820
|
+
sqrtPriceLimitX96: 0n
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
routerAddress = POTATOSWAP_V2_ROUTER;
|
|
825
|
+
swapData = encodeSwapExactTokensForETHSupportingFee(it.sellAmount, 0n, [tokenAddress, WOKB], it.sender, deadline);
|
|
826
|
+
}
|
|
827
|
+
if (i < 3) {
|
|
828
|
+
console.log(`[BundleSell] Wallet[${i}]: 使用 ${dexType} DEX Router 卖出, fee=${dexType === 'V3' ? lpFeeProfileToV3Fee(tokenState.lpFeeProfile) : 'N/A'}`);
|
|
829
|
+
}
|
|
830
|
+
const sellCallData = encodeExecute(routerAddress, 0n, swapData);
|
|
831
|
+
// 确保有足够余额
|
|
832
|
+
await this.aaManager.ensureSenderBalance(wallets[i], it.sender, parseOkb('0.0003'), `owner${i + 1}/sell-prefund`);
|
|
833
|
+
const { userOp } = await this.aaManager.buildUserOpWithFixedGas({
|
|
834
|
+
ownerWallet: wallets[i],
|
|
835
|
+
sender: it.sender,
|
|
836
|
+
callData: sellCallData,
|
|
837
|
+
nonce: it.nonce,
|
|
838
|
+
initCode: it.initCode,
|
|
839
|
+
deployed: it.initCode === '0x',
|
|
840
|
+
fixedGas: {
|
|
841
|
+
...(effConfig.fixedGas ?? {}),
|
|
842
|
+
callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_SELL,
|
|
843
|
+
},
|
|
844
|
+
});
|
|
845
|
+
const signed = await this.aaManager.signUserOp(userOp, wallets[i]);
|
|
846
|
+
return signed.userOp;
|
|
847
|
+
});
|
|
848
|
+
sellOps.push(...signedSells);
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
// ✅ 未毕业:使用 Portal 卖出
|
|
852
|
+
const signedSells = await mapWithConcurrency(sellItems, 4, async (it) => {
|
|
853
|
+
const i = it.i;
|
|
854
|
+
const signed = await this.buildSellUserOp(wallets[i], tokenAddress, it.sellAmount, it.sender, it.nonce, it.initCode, false, // needApprove=false,假设已预先授权
|
|
855
|
+
`owner${i + 1}`);
|
|
856
|
+
return signed.userOp;
|
|
857
|
+
});
|
|
858
|
+
sellOps.push(...signedSells);
|
|
859
|
+
}
|
|
767
860
|
}
|
|
768
|
-
const
|
|
861
|
+
const effConfigForSell = { ...(this.config ?? {}), ...(config ?? {}) };
|
|
769
862
|
const sellResult = await this.runHandleOps('sellBundle', sellOps, bundlerSigner, beneficiary, {
|
|
770
|
-
gasLimit:
|
|
771
|
-
gasPrice:
|
|
863
|
+
gasLimit: effConfigForSell.gasLimit,
|
|
864
|
+
gasPrice: effConfigForSell.minGasPriceGwei ? ethers.parseUnits(effConfigForSell.minGasPriceGwei.toString(), 'gwei') : undefined
|
|
772
865
|
});
|
|
773
866
|
if (!sellResult) {
|
|
774
867
|
throw new Error('卖出交易失败');
|
|
@@ -819,8 +912,8 @@ export class BundleExecutor {
|
|
|
819
912
|
}
|
|
820
913
|
if (withdrawOps.length > 0) {
|
|
821
914
|
withdrawResult = await this.runHandleOps('withdrawBundle', withdrawOps, bundlerSigner, beneficiary, {
|
|
822
|
-
gasLimit:
|
|
823
|
-
gasPrice:
|
|
915
|
+
gasLimit: effConfigForSell.gasLimit,
|
|
916
|
+
gasPrice: effConfigForSell.minGasPriceGwei ? ethers.parseUnits(effConfigForSell.minGasPriceGwei.toString(), 'gwei') : undefined
|
|
824
917
|
}) ?? undefined;
|
|
825
918
|
}
|
|
826
919
|
}
|
|
@@ -1573,7 +1666,6 @@ export class BundleExecutor {
|
|
|
1573
1666
|
const tokenState = await this.portalQuery.getTokenV7(tokenAddress);
|
|
1574
1667
|
// ✅ 内联曲线公式计算毕业容量
|
|
1575
1668
|
// 公式:graduationReserve = h * k(曲线参数从合约读取)
|
|
1576
|
-
const r = tokenState.r; // 衰减因子
|
|
1577
1669
|
const h = tokenState.h; // 初始高度
|
|
1578
1670
|
const k = tokenState.k; // 缩放系数
|
|
1579
1671
|
// 毕业储备 = h * k (wei)
|
|
@@ -1584,9 +1676,6 @@ export class BundleExecutor {
|
|
|
1584
1676
|
const graduateCapacity = graduationAmount ?? 73.38;
|
|
1585
1677
|
const remainingToGraduate = Math.max(0, graduateCapacity - parseFloat(poolReserveOkb));
|
|
1586
1678
|
// const graduateCapacity = parseFloat(graduationReserve);
|
|
1587
|
-
// 剩余毕业金额 = 毕业阈值 - 当前储备(加 0.5% 手续费缓冲)
|
|
1588
|
-
const remaining = Math.max(0, graduateCapacity - parseFloat(poolReserveOkb));
|
|
1589
|
-
const remainingWithFee = remaining * 1.005; // 0.5% 手续费缓冲
|
|
1590
1679
|
// const remainingToGraduate = remainingWithFee;
|
|
1591
1680
|
const remainingToGraduateWei = ethers.parseEther(remainingToGraduate.toFixed(18));
|
|
1592
1681
|
//
|
|
@@ -1773,10 +1862,13 @@ export class BundleExecutor {
|
|
|
1773
1862
|
let routerAddress;
|
|
1774
1863
|
if (dexType === 'V3') {
|
|
1775
1864
|
routerAddress = POTATOSWAP_V3_ROUTER;
|
|
1865
|
+
// ✅ 使用 getTokenV7 获取的 lpFeeProfile 动态计算 V3 fee
|
|
1866
|
+
// lpFeeProfile: 0 → 2500 (0.25%), 1 → 100 (0.01%), 2 → 10000 (1%)
|
|
1867
|
+
const v3Fee = lpFeeProfileToV3Fee(tokenState.lpFeeProfile);
|
|
1776
1868
|
swapData = encodeSwapExactETHForTokensV3({
|
|
1777
1869
|
tokenIn: WOKB,
|
|
1778
1870
|
tokenOut: tokenAddress,
|
|
1779
|
-
fee:
|
|
1871
|
+
fee: v3Fee,
|
|
1780
1872
|
recipient: info.sender,
|
|
1781
1873
|
deadline,
|
|
1782
1874
|
amountIn: amount,
|
|
@@ -69,7 +69,7 @@ export declare const ENTRYPOINT_ABI: readonly ["function getNonce(address sender
|
|
|
69
69
|
/** SimpleAccount ABI */
|
|
70
70
|
export declare const SIMPLE_ACCOUNT_ABI: readonly ["function execute(address dest, uint256 value, bytes func) external", "function executeBatch(address[] calldata dest, bytes[] calldata func) external", "function executeBatch(address[] calldata dest, uint256[] calldata values, bytes[] calldata func) external"];
|
|
71
71
|
/** Flap Portal ABI */
|
|
72
|
-
export declare const PORTAL_ABI: readonly ["function swapExactInput((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData) params) payable returns (uint256)", "function swapExactInputV3((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData,bytes extensionData)) external payable returns (uint256)", "function buy(address token, address to, uint256 minAmount) external payable returns (uint256)", "function sell(address token, uint256 amount, uint256 minEth) external returns (uint256)", "function quoteExactInput((address inputToken,address outputToken,uint256 inputAmount)) external returns (uint256)", "function previewBuy(address token, uint256 ethAmount) external view returns (uint256)", "function previewSell(address token, uint256 tokenAmount) external view returns (uint256)", "function getTokenV5(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32))", "function getTokenV7(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32,
|
|
72
|
+
export declare const PORTAL_ABI: readonly ["function swapExactInput((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData) params) payable returns (uint256)", "function swapExactInputV3((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData,bytes extensionData)) external payable returns (uint256)", "function buy(address token, address to, uint256 minAmount) external payable returns (uint256)", "function sell(address token, uint256 amount, uint256 minEth) external returns (uint256)", "function quoteExactInput((address inputToken,address outputToken,uint256 inputAmount)) external returns (uint256)", "function previewBuy(address token, uint256 ethAmount) external view returns (uint256)", "function previewSell(address token, uint256 tokenAmount) external view returns (uint256)", "function getTokenV5(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32))", "function getTokenV7(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32,uint16,address,uint256,uint8))", "function newTokenV2((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData) params) external payable returns (address)", "function newTokenV3((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData,bytes32 extensionID,bytes extensionData) params) external payable returns (address)", "function newTokenV4((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData,bytes32 extensionID,bytes extensionData,uint8 dexId,uint8 lpFeeProfile) params) external payable returns (address)"];
|
|
73
73
|
/** ERC20 ABI */
|
|
74
74
|
export declare const ERC20_ABI: readonly ["function balanceOf(address account) view returns (uint256)", "function allowance(address owner, address spender) view returns (uint256)", "function approve(address spender, uint256 amount) returns (bool)", "function transfer(address to, uint256 amount) returns (bool)", "function decimals() view returns (uint8)", "function symbol() view returns (string)", "function name() view returns (string)"];
|
|
75
75
|
/** PotatoSwap V2 Router ABI */
|
package/dist/xlayer/constants.js
CHANGED
|
@@ -115,7 +115,8 @@ export const PORTAL_ABI = [
|
|
|
115
115
|
'function previewBuy(address token, uint256 ethAmount) external view returns (uint256)',
|
|
116
116
|
'function previewSell(address token, uint256 tokenAmount) external view returns (uint256)',
|
|
117
117
|
'function getTokenV5(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32))',
|
|
118
|
-
|
|
118
|
+
// ✅ 修复:getTokenV7 返回 16 个字段,与 BSC portal.ts 一致
|
|
119
|
+
'function getTokenV7(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32,uint16,address,uint256,uint8))',
|
|
119
120
|
'function newTokenV2((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData) params) external payable returns (address)',
|
|
120
121
|
'function newTokenV3((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData,bytes32 extensionID,bytes extensionData) params) external payable returns (address)',
|
|
121
122
|
'function newTokenV4((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData,bytes32 extensionID,bytes extensionData,uint8 dexId,uint8 lpFeeProfile) params) external payable returns (address)',
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { XLayerConfig, BundleSwapSignParams, BundleSwapSignResult, BundleBatchSwapSignParams, BundleBatchSwapSignResult } from './types.js';
|
|
5
5
|
/**
|
|
6
6
|
* XLayer AA 外盘换手执行器
|
|
7
|
+
* ✅ 支持 V2 和 V3 交易
|
|
7
8
|
*/
|
|
8
9
|
export declare class AADexSwapExecutor {
|
|
9
10
|
private aaManager;
|
|
@@ -13,16 +14,27 @@ export declare class AADexSwapExecutor {
|
|
|
13
14
|
constructor(config?: XLayerConfig);
|
|
14
15
|
/**
|
|
15
16
|
* 获取 Router 地址
|
|
17
|
+
* @param tradeType - 交易类型:'V2' 或 'V3'
|
|
16
18
|
*/
|
|
17
19
|
private getEffectiveRouter;
|
|
20
|
+
/**
|
|
21
|
+
* 判断是否使用 V3
|
|
22
|
+
*/
|
|
23
|
+
private isV3;
|
|
24
|
+
/**
|
|
25
|
+
* 获取 V3 swap 的 deadline
|
|
26
|
+
*/
|
|
27
|
+
private getDexDeadline;
|
|
18
28
|
/**
|
|
19
29
|
* AA 外盘单钱包换手签名
|
|
30
|
+
* ✅ 支持 V2 和 V3 交易
|
|
20
31
|
*/
|
|
21
32
|
bundleSwapSign(params: BundleSwapSignParams & {
|
|
22
33
|
skipApprovalCheck?: boolean;
|
|
23
34
|
}): Promise<BundleSwapSignResult>;
|
|
24
35
|
/**
|
|
25
36
|
* AA 外盘批量换手签名
|
|
37
|
+
* ✅ 支持 V2 和 V3 交易
|
|
26
38
|
*/
|
|
27
39
|
bundleBatchSwapSign(params: BundleBatchSwapSignParams & {
|
|
28
40
|
skipApprovalCheck?: boolean;
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Wallet, ethers } from 'ethers';
|
|
5
5
|
import { AANonceMap, } from './types.js';
|
|
6
|
-
import { POTATOSWAP_V2_ROUTER, WOKB, MULTICALL3, } from './constants.js';
|
|
6
|
+
import { POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, WOKB, MULTICALL3, } from './constants.js';
|
|
7
7
|
import { AAAccountManager, encodeExecute } from './aa-account.js';
|
|
8
|
-
import { encodeApproveCall, } from './portal-ops.js';
|
|
9
|
-
import { DexQuery, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, } from './dex.js';
|
|
8
|
+
import { encodeApproveCall, lpFeeProfileToV3Fee, } from './portal-ops.js';
|
|
9
|
+
import { DexQuery, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHV3, } from './dex.js';
|
|
10
10
|
import { BundleExecutor } from './bundle.js';
|
|
11
11
|
import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
12
12
|
const multicallIface = new ethers.Interface([
|
|
@@ -45,11 +45,9 @@ function calculateProfitWei(amountWei, profitBps) {
|
|
|
45
45
|
return 0n;
|
|
46
46
|
return (amountWei * BigInt(profitBps)) / 10000n;
|
|
47
47
|
}
|
|
48
|
-
function getDexDeadline(minutes = 20) {
|
|
49
|
-
return Math.floor(Date.now() / 1000) + minutes * 60;
|
|
50
|
-
}
|
|
51
48
|
/**
|
|
52
49
|
* XLayer AA 外盘换手执行器
|
|
50
|
+
* ✅ 支持 V2 和 V3 交易
|
|
53
51
|
*/
|
|
54
52
|
export class AADexSwapExecutor {
|
|
55
53
|
constructor(config = {}) {
|
|
@@ -63,23 +61,41 @@ export class AADexSwapExecutor {
|
|
|
63
61
|
}
|
|
64
62
|
/**
|
|
65
63
|
* 获取 Router 地址
|
|
64
|
+
* @param tradeType - 交易类型:'V2' 或 'V3'
|
|
66
65
|
*/
|
|
67
66
|
getEffectiveRouter(params) {
|
|
68
67
|
if (params.routerAddress)
|
|
69
68
|
return params.routerAddress;
|
|
69
|
+
// ✅ 支持 V3 Router
|
|
70
|
+
if (params.tradeType === 'V3')
|
|
71
|
+
return POTATOSWAP_V3_ROUTER;
|
|
70
72
|
if (params.dexKey === 'DYORSWAP') {
|
|
71
73
|
return '0xfb001fbbace32f09cb6d3c449b935183de53ee96';
|
|
72
74
|
}
|
|
73
75
|
return POTATOSWAP_V2_ROUTER;
|
|
74
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* 判断是否使用 V3
|
|
79
|
+
*/
|
|
80
|
+
isV3(tradeType) {
|
|
81
|
+
return tradeType === 'V3';
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 获取 V3 swap 的 deadline
|
|
85
|
+
*/
|
|
86
|
+
getDexDeadline(minutes = 20) {
|
|
87
|
+
return Math.floor(Date.now() / 1000) + minutes * 60;
|
|
88
|
+
}
|
|
75
89
|
/**
|
|
76
90
|
* AA 外盘单钱包换手签名
|
|
91
|
+
* ✅ 支持 V2 和 V3 交易
|
|
77
92
|
*/
|
|
78
93
|
async bundleSwapSign(params) {
|
|
79
|
-
const { dexKey, routerAddress: routerAddressIn, tokenAddress, sellerPrivateKey, buyerPrivateKey, sellAmount, sellPercent = 100, buyAmountOkb, slippageBps = 100, disperseHopCount: disperseHopCountIn, payerPrivateKey, beneficiary: beneficiaryIn, payerStartNonce, routeAddress, skipApprovalCheck = false, } = params;
|
|
94
|
+
const { dexKey, routerAddress: routerAddressIn, tokenAddress, sellerPrivateKey, buyerPrivateKey, sellAmount, sellPercent = 100, buyAmountOkb, slippageBps = 100, disperseHopCount: disperseHopCountIn, payerPrivateKey, beneficiary: beneficiaryIn, payerStartNonce, routeAddress, skipApprovalCheck = false, tradeType, lpFeeProfile = 0, } = params;
|
|
80
95
|
const effectiveConfig = { ...(this.config ?? {}), ...(params.config ?? {}) };
|
|
81
96
|
const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effectiveConfig);
|
|
82
|
-
const
|
|
97
|
+
const isV3Trade = this.isV3(tradeType);
|
|
98
|
+
const effectiveRouter = this.getEffectiveRouter({ dexKey, routerAddress: routerAddressIn, tradeType });
|
|
83
99
|
const provider = this.aaManager.getProvider();
|
|
84
100
|
const sellerOwner = new Wallet(sellerPrivateKey, provider);
|
|
85
101
|
const buyerOwner = new Wallet(buyerPrivateKey, provider);
|
|
@@ -157,8 +173,26 @@ export class AADexSwapExecutor {
|
|
|
157
173
|
const signedApprove = await this.aaManager.signUserOp(userOp, sellerOwner);
|
|
158
174
|
outOps.push(signedApprove.userOp);
|
|
159
175
|
}
|
|
160
|
-
// Sell op
|
|
161
|
-
const
|
|
176
|
+
// Sell op - ✅ 支持 V2 和 V3
|
|
177
|
+
const deadline = this.getDexDeadline();
|
|
178
|
+
let sellSwapData;
|
|
179
|
+
if (isV3Trade) {
|
|
180
|
+
// V3: 使用 exactInputSingle
|
|
181
|
+
sellSwapData = encodeSwapExactTokensForETHV3({
|
|
182
|
+
tokenIn: tokenAddress,
|
|
183
|
+
tokenOut: WOKB,
|
|
184
|
+
fee: lpFeeProfileToV3Fee(lpFeeProfile),
|
|
185
|
+
recipient: sellerSender,
|
|
186
|
+
deadline,
|
|
187
|
+
amountIn: sellAmountWei,
|
|
188
|
+
amountOutMinimum: 0n,
|
|
189
|
+
sqrtPriceLimitX96: 0n,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// V2: 使用 swapExactTokensForETHSupportingFeeOnTransferTokens
|
|
194
|
+
sellSwapData = encodeSwapExactTokensForETHSupportingFee(sellAmountWei, 0n, [tokenAddress, WOKB], sellerSender, deadline);
|
|
195
|
+
}
|
|
162
196
|
const sellCallData = encodeExecute(effectiveRouter, 0n, sellSwapData);
|
|
163
197
|
const signedSell = await this.aaManager.buildUserOpWithState({
|
|
164
198
|
ownerWallet: sellerOwner,
|
|
@@ -183,8 +217,25 @@ export class AADexSwapExecutor {
|
|
|
183
217
|
});
|
|
184
218
|
outOps.push(signedTransfer.userOp);
|
|
185
219
|
}
|
|
186
|
-
// Buy op
|
|
187
|
-
|
|
220
|
+
// Buy op - ✅ 支持 V2 和 V3
|
|
221
|
+
let buySwapData;
|
|
222
|
+
if (isV3Trade) {
|
|
223
|
+
// V3: 使用 exactInputSingle(注意:V3 买入需要用 WOKB 作为输入)
|
|
224
|
+
buySwapData = encodeSwapExactETHForTokensV3({
|
|
225
|
+
tokenIn: WOKB,
|
|
226
|
+
tokenOut: tokenAddress,
|
|
227
|
+
fee: lpFeeProfileToV3Fee(lpFeeProfile),
|
|
228
|
+
recipient: buyerSender,
|
|
229
|
+
deadline,
|
|
230
|
+
amountIn: finalBuyAmountWei,
|
|
231
|
+
amountOutMinimum: 0n,
|
|
232
|
+
sqrtPriceLimitX96: 0n,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
// V2: 使用 swapExactETHForTokensSupportingFeeOnTransferTokens
|
|
237
|
+
buySwapData = encodeSwapExactETHForTokensSupportingFee(0n, [WOKB, tokenAddress], buyerSender, deadline);
|
|
238
|
+
}
|
|
188
239
|
const buyCallData = encodeExecute(effectiveRouter, finalBuyAmountWei, buySwapData);
|
|
189
240
|
const signedBuy = await this.aaManager.buildUserOpWithState({
|
|
190
241
|
ownerWallet: buyerOwner,
|
|
@@ -250,12 +301,14 @@ export class AADexSwapExecutor {
|
|
|
250
301
|
}
|
|
251
302
|
/**
|
|
252
303
|
* AA 外盘批量换手签名
|
|
304
|
+
* ✅ 支持 V2 和 V3 交易
|
|
253
305
|
*/
|
|
254
306
|
async bundleBatchSwapSign(params) {
|
|
255
|
-
const { dexKey, routerAddress: routerAddressIn, tokenAddress, sellerPrivateKey, buyerPrivateKeys, buyAmountsOkb, sellAmount, sellPercent = 100, disperseHopCount: disperseHopCountIn, payerPrivateKey, beneficiary: beneficiaryIn, payerStartNonce, routeAddress, skipApprovalCheck = false, } = params;
|
|
307
|
+
const { dexKey, routerAddress: routerAddressIn, tokenAddress, sellerPrivateKey, buyerPrivateKeys, buyAmountsOkb, sellAmount, sellPercent = 100, disperseHopCount: disperseHopCountIn, payerPrivateKey, beneficiary: beneficiaryIn, payerStartNonce, routeAddress, skipApprovalCheck = false, tradeType, lpFeeProfile = 0, } = params;
|
|
256
308
|
const effectiveConfig = { ...(this.config ?? {}), ...(params.config ?? {}) };
|
|
257
309
|
const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effectiveConfig);
|
|
258
|
-
const
|
|
310
|
+
const isV3Trade = this.isV3(tradeType);
|
|
311
|
+
const effectiveRouter = this.getEffectiveRouter({ dexKey, routerAddress: routerAddressIn, tradeType });
|
|
259
312
|
const provider = this.aaManager.getProvider();
|
|
260
313
|
const sellerOwner = new Wallet(sellerPrivateKey, provider);
|
|
261
314
|
const buyerOwners = buyerPrivateKeys.map(pk => new Wallet(pk, provider));
|
|
@@ -311,8 +364,26 @@ export class AADexSwapExecutor {
|
|
|
311
364
|
const signedApprove = await this.aaManager.signUserOp(userOp, sellerOwner);
|
|
312
365
|
outOps.push(signedApprove.userOp);
|
|
313
366
|
}
|
|
314
|
-
// Sell op
|
|
315
|
-
const
|
|
367
|
+
// Sell op - ✅ 支持 V2 和 V3
|
|
368
|
+
const deadline = this.getDexDeadline();
|
|
369
|
+
let sellSwapData;
|
|
370
|
+
if (isV3Trade) {
|
|
371
|
+
// V3: 使用 exactInputSingle
|
|
372
|
+
sellSwapData = encodeSwapExactTokensForETHV3({
|
|
373
|
+
tokenIn: tokenAddress,
|
|
374
|
+
tokenOut: WOKB,
|
|
375
|
+
fee: lpFeeProfileToV3Fee(lpFeeProfile),
|
|
376
|
+
recipient: sellerAi.sender,
|
|
377
|
+
deadline,
|
|
378
|
+
amountIn: sellAmountWei,
|
|
379
|
+
amountOutMinimum: 0n,
|
|
380
|
+
sqrtPriceLimitX96: 0n,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
// V2: 使用 swapExactTokensForETHSupportingFeeOnTransferTokens
|
|
385
|
+
sellSwapData = encodeSwapExactTokensForETHSupportingFee(sellAmountWei, 0n, [tokenAddress, WOKB], sellerAi.sender, deadline);
|
|
386
|
+
}
|
|
316
387
|
const sellCallData = encodeExecute(effectiveRouter, 0n, sellSwapData);
|
|
317
388
|
const signedSell = await this.aaManager.buildUserOpWithState({
|
|
318
389
|
ownerWallet: sellerOwner,
|
|
@@ -427,11 +498,28 @@ export class AADexSwapExecutor {
|
|
|
427
498
|
}
|
|
428
499
|
}
|
|
429
500
|
}
|
|
430
|
-
// Batch Buy ops
|
|
501
|
+
// Batch Buy ops(分发后再执行)- ✅ 支持 V2 和 V3
|
|
431
502
|
for (let i = 0; i < buyerOwners.length; i++) {
|
|
432
503
|
const ai = buyerAis[i];
|
|
433
504
|
const buyWei = buyAmountsWei[i];
|
|
434
|
-
|
|
505
|
+
let buySwapData;
|
|
506
|
+
if (isV3Trade) {
|
|
507
|
+
// V3: 使用 exactInputSingle
|
|
508
|
+
buySwapData = encodeSwapExactETHForTokensV3({
|
|
509
|
+
tokenIn: WOKB,
|
|
510
|
+
tokenOut: tokenAddress,
|
|
511
|
+
fee: lpFeeProfileToV3Fee(lpFeeProfile),
|
|
512
|
+
recipient: ai.sender,
|
|
513
|
+
deadline,
|
|
514
|
+
amountIn: buyWei,
|
|
515
|
+
amountOutMinimum: 0n,
|
|
516
|
+
sqrtPriceLimitX96: 0n,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
// V2: 使用 swapExactETHForTokensSupportingFeeOnTransferTokens
|
|
521
|
+
buySwapData = encodeSwapExactETHForTokensSupportingFee(0n, [WOKB, tokenAddress], ai.sender, deadline);
|
|
522
|
+
}
|
|
435
523
|
const buyCallData = encodeExecute(effectiveRouter, buyWei, buySwapData);
|
|
436
524
|
const signedBuy = await this.aaManager.buildUserOpWithState({
|
|
437
525
|
ownerWallet: buyerOwners[i],
|
|
@@ -180,6 +180,8 @@ export declare class PortalQuery {
|
|
|
180
180
|
}>;
|
|
181
181
|
/**
|
|
182
182
|
* 获取代币状态 V7 (包含 lpFeeProfile)
|
|
183
|
+
*
|
|
184
|
+
* ✅ 修复:与 BSC portal.ts 对齐,使用 16 字段结构
|
|
183
185
|
*/
|
|
184
186
|
getTokenV7(tokenAddress: string): Promise<{
|
|
185
187
|
status: number;
|
|
@@ -194,7 +196,9 @@ export declare class PortalQuery {
|
|
|
194
196
|
quoteTokenAddress: string;
|
|
195
197
|
nativeToQuoteSwapEnabled: boolean;
|
|
196
198
|
extensionID: string;
|
|
197
|
-
|
|
199
|
+
taxRate: number;
|
|
200
|
+
pool: string;
|
|
201
|
+
progress: bigint;
|
|
198
202
|
lpFeeProfile: number;
|
|
199
203
|
}>;
|
|
200
204
|
/**
|
|
@@ -224,6 +224,8 @@ export class PortalQuery {
|
|
|
224
224
|
}
|
|
225
225
|
/**
|
|
226
226
|
* 获取代币状态 V7 (包含 lpFeeProfile)
|
|
227
|
+
*
|
|
228
|
+
* ✅ 修复:与 BSC portal.ts 对齐,使用 16 字段结构
|
|
227
229
|
*/
|
|
228
230
|
async getTokenV7(tokenAddress) {
|
|
229
231
|
const raw = await this.portal.getTokenV7(tokenAddress);
|
|
@@ -240,8 +242,10 @@ export class PortalQuery {
|
|
|
240
242
|
quoteTokenAddress: raw[9],
|
|
241
243
|
nativeToQuoteSwapEnabled: raw[10],
|
|
242
244
|
extensionID: raw[11],
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
taxRate: Number(raw[12]),
|
|
246
|
+
pool: raw[13],
|
|
247
|
+
progress: raw[14],
|
|
248
|
+
lpFeeProfile: Number(raw[15]),
|
|
245
249
|
};
|
|
246
250
|
}
|
|
247
251
|
/**
|
package/dist/xlayer/types.d.ts
CHANGED
|
@@ -361,6 +361,8 @@ export interface BundleSwapParams {
|
|
|
361
361
|
dexKey?: string;
|
|
362
362
|
/** 显式指定 Router 地址(若不传则由 SDK 根据 dexKey 自动决定) */
|
|
363
363
|
routerAddress?: string;
|
|
364
|
+
/** LP 费率档位 (V3 专用): 0=STANDARD (0.25%), 1=LOW (0.01%), 2=HIGH (1%) */
|
|
365
|
+
lpFeeProfile?: number;
|
|
364
366
|
/** 代币地址 */
|
|
365
367
|
tokenAddress: string;
|
|
366
368
|
/** 卖方 Owner 私钥 */
|
|
@@ -480,6 +482,8 @@ export interface BundleBatchSwapParams {
|
|
|
480
482
|
dexKey?: string;
|
|
481
483
|
/** 显式指定 Router 地址(若不传则由 SDK 根据 dexKey 自动决定) */
|
|
482
484
|
routerAddress?: string;
|
|
485
|
+
/** LP 费率档位 (V3 专用): 0=STANDARD (0.25%), 1=LOW (0.01%), 2=HIGH (1%) */
|
|
486
|
+
lpFeeProfile?: number;
|
|
483
487
|
/** 代币地址 */
|
|
484
488
|
tokenAddress: string;
|
|
485
489
|
/** 卖方 Owner 私钥 */
|