four-flap-meme-sdk 1.6.4 → 1.6.6
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/contracts/tm-bundle-merkle/swap.js +1 -2
- package/dist/contracts/tm-bundle-merkle/utils.js +1 -2
- package/dist/flap/portal-bundle-merkle/swap.js +1 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -0
- package/dist/pancake/bundle-buy-first.js +2 -2
- package/dist/pancake/bundle-swap.js +5 -6
- package/dist/utils/holders-maker.js +1 -1
- package/dist/xlayer/aa-account.d.ts +6 -0
- package/dist/xlayer/aa-account.js +12 -0
- package/dist/xlayer/bundle.js +24 -9
- package/dist/xlayer/dex-aa-buy.d.ts +63 -0
- package/dist/xlayer/dex-aa-buy.js +318 -0
- package/dist/xlayer/dex-aa-sell.d.ts +70 -0
- package/dist/xlayer/dex-aa-sell.js +416 -0
- package/dist/xlayer/dex.js +1 -2
- package/dist/xlayer/index.d.ts +2 -0
- package/dist/xlayer/index.js +8 -0
- package/package.json +1 -1
|
@@ -742,14 +742,13 @@ export async function fourQuickBatchSwapMerkle(params) {
|
|
|
742
742
|
});
|
|
743
743
|
signedTransactions.push(...profitHopResult.signedTransactions);
|
|
744
744
|
profitHopWallets = profitHopResult.hopWallets; // ✅ 收集利润多跳钱包
|
|
745
|
-
|
|
745
|
+
// 多跳交易已签名
|
|
746
746
|
}
|
|
747
747
|
console.log(`[fourQuickBatchSwapMerkle] 交易组装完成: ${signedTransactions.length} 笔`);
|
|
748
748
|
console.log(` - 贿赂: ${bribeTx ? 1 : 0}`);
|
|
749
749
|
console.log(` - 卖出: 1`);
|
|
750
750
|
console.log(` - 转账: ${transferTxs.length}`);
|
|
751
751
|
console.log(` - 买入: ${signedBuys.length}`);
|
|
752
|
-
console.log(` - 利润多跳: ${profitAmount > 0n ? PROFIT_HOP_COUNT + 1 : 0}`);
|
|
753
752
|
return {
|
|
754
753
|
signedTransactions,
|
|
755
754
|
disperseHopWallets: allHopWallets.length > 0 ? allHopWallets : undefined, // ✅ 返回转账多跳钱包
|
|
@@ -196,8 +196,7 @@ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainI
|
|
|
196
196
|
catch (error) {
|
|
197
197
|
console.warn(`[getTokenToNativeQuote] 报价失败:`, error);
|
|
198
198
|
}
|
|
199
|
-
//
|
|
200
|
-
console.log(`[getTokenToNativeQuote] 无法获取报价,使用最低利润: ${MIN_PROFIT_WEI}`);
|
|
199
|
+
// 报价失败,返回最低金额
|
|
201
200
|
return MIN_PROFIT_WEI;
|
|
202
201
|
}
|
|
203
202
|
/**
|
|
@@ -1195,14 +1195,13 @@ export async function flapQuickBatchSwapMerkle(params) {
|
|
|
1195
1195
|
signedTransactions.push(...profitResult.signedTransactions);
|
|
1196
1196
|
profitHopWallets = profitResult.hopWallets; // ✅ 收集利润多跳钱包
|
|
1197
1197
|
}
|
|
1198
|
-
|
|
1198
|
+
// 多跳交易已签名
|
|
1199
1199
|
}
|
|
1200
1200
|
console.log(`[flapQuickBatchSwapMerkle] 交易组装完成: ${signedTransactions.length} 笔`);
|
|
1201
1201
|
console.log(` - 贿赂: ${bribeTx ? 1 : 0}`);
|
|
1202
1202
|
console.log(` - 卖出: 1`);
|
|
1203
1203
|
console.log(` - 转账: ${transferTxs.length}`);
|
|
1204
1204
|
console.log(` - 买入: ${signedBuys.length}`);
|
|
1205
|
-
console.log(` - 利润多跳: ${nativeProfitAmount > 0n ? PROFIT_HOP_COUNT + 1 : 0}`);
|
|
1206
1205
|
return {
|
|
1207
1206
|
signedTransactions,
|
|
1208
1207
|
disperseHopWallets: allHopWallets.length > 0 ? allHopWallets : undefined, // ✅ 返回转账多跳钱包
|
package/dist/index.d.ts
CHANGED
|
@@ -54,4 +54,4 @@ submitDirectToRpcSequentialNoWait, // ✅ 顺序广播但不等待确认(用
|
|
|
54
54
|
submitDirectToRpcParallel, type DirectSubmitConfig, type DirectSubmitResult, type DirectTxResult } from './contracts/tm-bundle-merkle/submit.js';
|
|
55
55
|
export { directV2BatchBuy, directV2BatchSell, directV3BatchBuy, directV3BatchSell, getRouterAddress, DIRECT_ROUTERS, type DirectV2BuyParams, type DirectV2SellParams, type DirectV3BuyParams, type DirectV3SellParams, type DirectRouterResult, type DirectRouterSignConfig, type DexKey, type RouterVersion, } from './dex/index.js';
|
|
56
56
|
export * as XLayer from './xlayer/index.js';
|
|
57
|
-
export { bundleBuy as xlayerBundleBuy, bundleSell as xlayerBundleSell, bundleBuySell as xlayerBundleBuySell, bundleCreateBuySign as xlayerBundleCreateBuySign, bundleCreateToDexSign as xlayerBundleCreateToDexSign, bundleGraduateBuy as xlayerBundleGraduateBuy, bundleSwapSign as xlayerBundleSwapSign, bundleBatchSwapSign as xlayerBundleBatchSwapSign, createBundleExecutor as xlayerCreateBundleExecutor, BundleExecutor as XLayerBundleExecutor, makeVolume as xlayerMakeVolume, singleRoundVolume as xlayerSingleRoundVolume, createVolumeExecutor as xlayerCreateVolumeExecutor, VolumeExecutor as XLayerVolumeExecutor, makeBuyFirstVolume as xlayerMakeBuyFirstVolume, BuyFirstVolumeExecutor as XLayerBuyFirstVolumeExecutor, AAPortalBuyFirstExecutor as XLayerAAPortalBuyFirstExecutor, AADexBuyFirstExecutor as XLayerAADexBuyFirstExecutor, AAVolumeBuyFirstExecutor as XLayerAAVolumeBuyFirstExecutor, createAAVolumeBuyFirstExecutor as xlayerCreateAAVolumeBuyFirstExecutor, type VolumeBuyFirstParams as XLayerVolumeBuyFirstParams, type VolumeBuyFirstResult as XLayerVolumeBuyFirstResult, type VolumeContinuousParams as XLayerVolumeContinuousParams, type VolumeContinuousResult as XLayerVolumeContinuousResult, type VolumeProgress as XLayerVolumeProgress, createDexExecutor as xlayerCreateDexExecutor, createDexQuery as xlayerCreateDexQuery, quoteOkbToToken as xlayerQuoteOkbToToken, quoteTokenToOkb as xlayerQuoteTokenToOkb, DexExecutor as XLayerDexExecutor, DexQuery as XLayerDexQuery, createAAAccountManager as xlayerCreateAAAccountManager, predictSender as xlayerPredictSender, createWallet as xlayerCreateWallet, AAAccountManager as XLayerAAAccountManager, generateAAWallets as xlayerGenerateAAWallets, generateAAWalletsFromMnemonic as xlayerGenerateAAWalletsFromMnemonic, predictSendersFromPrivateKeys as xlayerPredictSendersFromPrivateKeys, createBundlerClient as xlayerCreateBundlerClient, BundlerClient as XLayerBundlerClient, createPortalQuery as xlayerCreatePortalQuery, PortalQuery as XLayerPortalQuery, encodeBuyCall as xlayerEncodeBuyCall, encodeSellCall as xlayerEncodeSellCall, encodeApproveCall as xlayerEncodeApproveCall, parseOkb as xlayerParseOkb, formatOkb as xlayerFormatOkb, XLAYER_CHAIN_ID, FLAP_PORTAL as XLAYER_FLAP_PORTAL, ENTRYPOINT_V06 as XLAYER_ENTRYPOINT, SIMPLE_ACCOUNT_FACTORY as XLAYER_FACTORY, PARTICLE_BUNDLER_URL as XLAYER_BUNDLER_URL, WOKB as XLAYER_WOKB, POTATOSWAP_V2_ROUTER as XLAYER_POTATOSWAP_ROUTER, type XLayerConfig, type BundleBuyParams as XLayerBundleBuyParams, type BundleSellParams as XLayerBundleSellParams, type BundleBuySellParams as XLayerBundleBuySellParams, type BundleSwapParams as XLayerBundleSwapParams, type BundleSwapSignParams as XLayerBundleSwapSignParams, type BundleBatchSwapParams as XLayerBundleBatchSwapParams, type BundleBatchSwapSignParams as XLayerBundleBatchSwapSignParams, type VolumeParams as XLayerVolumeParams, type BundleBuyResult as XLayerBundleBuyResult, type BundleSellResult as XLayerBundleSellResult, type BundleBuySellResult as XLayerBundleBuySellResult, type BundleCreateBuySignParams as XLayerBundleCreateBuySignParams, type BundleCreateBuySignResult as XLayerBundleCreateBuySignResult, type BundleCreateToDexSignParams as XLayerBundleCreateToDexSignParams, type BundleCreateToDexSignResult as XLayerBundleCreateToDexSignResult, type BundleGraduateBuyParams as XLayerBundleGraduateBuyParams, type BundleGraduateBuyResult as XLayerBundleGraduateBuyResult, type BundleSwapResult as XLayerBundleSwapResult, type BundleSwapSignResult as XLayerBundleSwapSignResult, type BundleBatchSwapResult as XLayerBundleBatchSwapResult, type BundleBatchSwapSignResult as XLayerBundleBatchSwapSignResult, type VolumeResult as XLayerVolumeResult, type BuyFirstParams as XLayerBuyFirstParams, type BuyFirstResult as XLayerBuyFirstResult, type BuyFirstVolumeParams as XLayerBuyFirstVolumeParams, type BuyFirstVolumeResult as XLayerBuyFirstVolumeResult, type HandleOpsResult as XLayerHandleOpsResult, type AAAccount as XLayerAAAccount, type UserOperation as XLayerUserOperation, type GeneratedAAWallet as XLayerGeneratedAAWallet, type GenerateAAWalletsParams as XLayerGenerateAAWalletsParams, type GenerateAAWalletsResult as XLayerGenerateAAWalletsResult, } from './xlayer/index.js';
|
|
57
|
+
export { bundleBuy as xlayerBundleBuy, bundleSell as xlayerBundleSell, bundleBuySell as xlayerBundleBuySell, bundleCreateBuySign as xlayerBundleCreateBuySign, bundleCreateToDexSign as xlayerBundleCreateToDexSign, bundleGraduateBuy as xlayerBundleGraduateBuy, bundleSwapSign as xlayerBundleSwapSign, bundleBatchSwapSign as xlayerBundleBatchSwapSign, createBundleExecutor as xlayerCreateBundleExecutor, BundleExecutor as XLayerBundleExecutor, makeVolume as xlayerMakeVolume, singleRoundVolume as xlayerSingleRoundVolume, createVolumeExecutor as xlayerCreateVolumeExecutor, VolumeExecutor as XLayerVolumeExecutor, makeBuyFirstVolume as xlayerMakeBuyFirstVolume, BuyFirstVolumeExecutor as XLayerBuyFirstVolumeExecutor, AAPortalBuyFirstExecutor as XLayerAAPortalBuyFirstExecutor, AADexBuyFirstExecutor as XLayerAADexBuyFirstExecutor, AAVolumeBuyFirstExecutor as XLayerAAVolumeBuyFirstExecutor, createAAVolumeBuyFirstExecutor as xlayerCreateAAVolumeBuyFirstExecutor, type VolumeBuyFirstParams as XLayerVolumeBuyFirstParams, type VolumeBuyFirstResult as XLayerVolumeBuyFirstResult, type VolumeContinuousParams as XLayerVolumeContinuousParams, type VolumeContinuousResult as XLayerVolumeContinuousResult, type VolumeProgress as XLayerVolumeProgress, createDexExecutor as xlayerCreateDexExecutor, createDexQuery as xlayerCreateDexQuery, quoteOkbToToken as xlayerQuoteOkbToToken, quoteTokenToOkb as xlayerQuoteTokenToOkb, DexExecutor as XLayerDexExecutor, DexQuery as XLayerDexQuery, buildDexBatchBuyOps, buildDexBatchBuyOpsV3, type DexBatchBuyParams, type DexBatchBuyV3Params, type DexBatchBuyResult, buildDexBatchSellOps, buildDexBatchSellOpsV3, type DexBatchSellParams, type DexBatchSellV3Params, type DexBatchSellResult, createAAAccountManager as xlayerCreateAAAccountManager, predictSender as xlayerPredictSender, createWallet as xlayerCreateWallet, AAAccountManager as XLayerAAAccountManager, generateAAWallets as xlayerGenerateAAWallets, generateAAWalletsFromMnemonic as xlayerGenerateAAWalletsFromMnemonic, predictSendersFromPrivateKeys as xlayerPredictSendersFromPrivateKeys, createBundlerClient as xlayerCreateBundlerClient, BundlerClient as XLayerBundlerClient, createPortalQuery as xlayerCreatePortalQuery, PortalQuery as XLayerPortalQuery, encodeBuyCall as xlayerEncodeBuyCall, encodeSellCall as xlayerEncodeSellCall, encodeApproveCall as xlayerEncodeApproveCall, parseOkb as xlayerParseOkb, formatOkb as xlayerFormatOkb, XLAYER_CHAIN_ID, FLAP_PORTAL as XLAYER_FLAP_PORTAL, ENTRYPOINT_V06 as XLAYER_ENTRYPOINT, SIMPLE_ACCOUNT_FACTORY as XLAYER_FACTORY, PARTICLE_BUNDLER_URL as XLAYER_BUNDLER_URL, WOKB as XLAYER_WOKB, POTATOSWAP_V2_ROUTER as XLAYER_POTATOSWAP_ROUTER, type XLayerConfig, type BundleBuyParams as XLayerBundleBuyParams, type BundleSellParams as XLayerBundleSellParams, type BundleBuySellParams as XLayerBundleBuySellParams, type BundleSwapParams as XLayerBundleSwapParams, type BundleSwapSignParams as XLayerBundleSwapSignParams, type BundleBatchSwapParams as XLayerBundleBatchSwapParams, type BundleBatchSwapSignParams as XLayerBundleBatchSwapSignParams, type VolumeParams as XLayerVolumeParams, type BundleBuyResult as XLayerBundleBuyResult, type BundleSellResult as XLayerBundleSellResult, type BundleBuySellResult as XLayerBundleBuySellResult, type BundleCreateBuySignParams as XLayerBundleCreateBuySignParams, type BundleCreateBuySignResult as XLayerBundleCreateBuySignResult, type BundleCreateToDexSignParams as XLayerBundleCreateToDexSignParams, type BundleCreateToDexSignResult as XLayerBundleCreateToDexSignResult, type BundleGraduateBuyParams as XLayerBundleGraduateBuyParams, type BundleGraduateBuyResult as XLayerBundleGraduateBuyResult, type BundleSwapResult as XLayerBundleSwapResult, type BundleSwapSignResult as XLayerBundleSwapSignResult, type BundleBatchSwapResult as XLayerBundleBatchSwapResult, type BundleBatchSwapSignResult as XLayerBundleBatchSwapSignResult, type VolumeResult as XLayerVolumeResult, type BuyFirstParams as XLayerBuyFirstParams, type BuyFirstResult as XLayerBuyFirstResult, type BuyFirstVolumeParams as XLayerBuyFirstVolumeParams, type BuyFirstVolumeResult as XLayerBuyFirstVolumeResult, type HandleOpsResult as XLayerHandleOpsResult, type AAAccount as XLayerAAAccount, type UserOperation as XLayerUserOperation, type GeneratedAAWallet as XLayerGeneratedAAWallet, type GenerateAAWalletsParams as XLayerGenerateAAWalletsParams, type GenerateAAWalletsResult as XLayerGenerateAAWalletsResult, } from './xlayer/index.js';
|
package/dist/index.js
CHANGED
|
@@ -112,6 +112,10 @@ makeBuyFirstVolume as xlayerMakeBuyFirstVolume, BuyFirstVolumeExecutor as XLayer
|
|
|
112
112
|
AAVolumeBuyFirstExecutor as XLayerAAVolumeBuyFirstExecutor, createAAVolumeBuyFirstExecutor as xlayerCreateAAVolumeBuyFirstExecutor,
|
|
113
113
|
// DEX 交易
|
|
114
114
|
createDexExecutor as xlayerCreateDexExecutor, createDexQuery as xlayerCreateDexQuery, quoteOkbToToken as xlayerQuoteOkbToToken, quoteTokenToOkb as xlayerQuoteTokenToOkb, DexExecutor as XLayerDexExecutor, DexQuery as XLayerDexQuery,
|
|
115
|
+
// ✅ AA 批量买入(安全版:利润配置硬编码在 SDK 内部)
|
|
116
|
+
buildDexBatchBuyOps, buildDexBatchBuyOpsV3,
|
|
117
|
+
// ✅ AA 批量卖出(安全版:利润配置硬编码在 SDK 内部)
|
|
118
|
+
buildDexBatchSellOps, buildDexBatchSellOpsV3,
|
|
115
119
|
// AA 账户管理
|
|
116
120
|
createAAAccountManager as xlayerCreateAAAccountManager, predictSender as xlayerPredictSender, createWallet as xlayerCreateWallet, AAAccountManager as XLayerAAAccountManager,
|
|
117
121
|
// 批量生成钱包工具
|
|
@@ -194,7 +194,7 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
194
194
|
const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
|
|
195
195
|
const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
|
|
196
196
|
profitAmount = await getTokenToNativeQuote(context.provider, quoteToken, tokenProfitAmount, 'BSC', version, fee);
|
|
197
|
-
|
|
197
|
+
// ERC20→BNB 转换完成
|
|
198
198
|
}
|
|
199
199
|
// ✅ 获取贿赂金额
|
|
200
200
|
const bribeAmount = config.bribeAmount && config.bribeAmount > 0
|
|
@@ -656,7 +656,7 @@ async function pancakeBundleBuyFirstMultiWallet(params) {
|
|
|
656
656
|
const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
|
|
657
657
|
const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
|
|
658
658
|
profitAmount = await getTokenToNativeQuote(context.provider, quoteToken, tokenProfitAmount, 'BSC', version, fee);
|
|
659
|
-
|
|
659
|
+
// ERC20→BNB 转换完成
|
|
660
660
|
}
|
|
661
661
|
// ✅ 第三批并行:构建并签名所有交易
|
|
662
662
|
const allTransactions = [];
|
|
@@ -634,7 +634,7 @@ export async function pancakeBundleSwapMerkle(params) {
|
|
|
634
634
|
if (useNativeToken) {
|
|
635
635
|
// 输出是 BNB,直接计算利润(根据 userType 动态调整)
|
|
636
636
|
profitAmount = calculateProfitAmount(quoteResult.estimatedBNBOut, config.userType);
|
|
637
|
-
|
|
637
|
+
// 利润计算完成
|
|
638
638
|
}
|
|
639
639
|
else {
|
|
640
640
|
// 输出是 ERC20,需要先获取 ERC20 → BNB 的报价
|
|
@@ -643,7 +643,7 @@ export async function pancakeBundleSwapMerkle(params) {
|
|
|
643
643
|
const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, quoteResult.estimatedBNBOut, // 这实际上是 ERC20 数量
|
|
644
644
|
version, fee);
|
|
645
645
|
profitAmount = calculateProfitAmount(estimatedBNBValue, config.userType);
|
|
646
|
-
|
|
646
|
+
// ERC20→BNB 报价完成
|
|
647
647
|
}
|
|
648
648
|
// ✅ 获取贿赂金额
|
|
649
649
|
const bribeAmount = config.bribeAmount && config.bribeAmount > 0
|
|
@@ -954,14 +954,14 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
954
954
|
let profitAmount;
|
|
955
955
|
if (useNativeToken) {
|
|
956
956
|
profitAmount = calculateProfitAmount(estimatedBNBOut, config.userType);
|
|
957
|
-
|
|
957
|
+
// 利润计算完成
|
|
958
958
|
}
|
|
959
959
|
else {
|
|
960
960
|
const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
|
|
961
961
|
const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
|
|
962
962
|
const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, estimatedBNBOut, version, fee);
|
|
963
963
|
profitAmount = calculateProfitAmount(estimatedBNBValue, config.userType);
|
|
964
|
-
|
|
964
|
+
// ERC20→BNB 报价完成
|
|
965
965
|
}
|
|
966
966
|
// 计算利润 nonce
|
|
967
967
|
const profitNonce = profitAmount > 0n
|
|
@@ -1444,14 +1444,13 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
1444
1444
|
});
|
|
1445
1445
|
signedTransactions.push(...profitHopResult.signedTransactions);
|
|
1446
1446
|
profitHopWallets = profitHopResult.hopWallets; // ✅ 收集利润多跳钱包
|
|
1447
|
-
|
|
1447
|
+
// 多跳交易已签名
|
|
1448
1448
|
}
|
|
1449
1449
|
console.log(`[pancakeQuickBatchSwapMerkle] 交易组装完成: ${signedTransactions.length} 笔`);
|
|
1450
1450
|
console.log(` - 贿赂: ${bribeTx ? 1 : 0}`);
|
|
1451
1451
|
console.log(` - 卖出: 1`);
|
|
1452
1452
|
console.log(` - 转账: ${transferTxs.length}`);
|
|
1453
1453
|
console.log(` - 买入: ${signedBuys.length}`);
|
|
1454
|
-
console.log(` - 利润多跳: ${profitAmount > 0n ? PROFIT_HOP_COUNT + 1 : 0}`);
|
|
1455
1454
|
const outputUnit = useNativeToken ? 'BNB' : 'ERC20';
|
|
1456
1455
|
return {
|
|
1457
1456
|
signedTransactions,
|
|
@@ -644,7 +644,7 @@ export async function holdersMaker(params) {
|
|
|
644
644
|
const profitRateBps = PROFIT_CONFIG.RATE_BPS;
|
|
645
645
|
const totalProfit = (totalBuyAmountForProfit * BigInt(profitRateBps)) / 10000n;
|
|
646
646
|
const profitPerBatch = totalProfit / BigInt(walletBatches.length);
|
|
647
|
-
|
|
647
|
+
// 利润计算完成
|
|
648
648
|
// 6. 生成分发多跳路径(如果启用)
|
|
649
649
|
// XLayer 强制禁用分发多跳
|
|
650
650
|
let allDisperseHopWallets = [];
|
|
@@ -36,9 +36,15 @@ export declare class AAAccountManager {
|
|
|
36
36
|
private deployedSenderSet;
|
|
37
37
|
private feeDataCache?;
|
|
38
38
|
private readonly feeDataCacheTtlMs;
|
|
39
|
+
private entryPointSynced;
|
|
39
40
|
private defaultGasPolicy?;
|
|
40
41
|
private defaultFixedGas?;
|
|
41
42
|
constructor(config?: XLayerConfig);
|
|
43
|
+
/**
|
|
44
|
+
* ✅ 同步 EntryPoint(带缓存,避免重复 RPC 调用)
|
|
45
|
+
*
|
|
46
|
+
* 优化:EntryPoint 地址在运行期间几乎不会变化,只需同步一次
|
|
47
|
+
*/
|
|
42
48
|
private syncEntryPointIfNeeded;
|
|
43
49
|
/**
|
|
44
50
|
* 获取 Provider
|
|
@@ -29,6 +29,8 @@ export class AAAccountManager {
|
|
|
29
29
|
// PERF: 本文件的改造会尽量减少逐地址 RPC 调用
|
|
30
30
|
this.deployedSenderSet = new Set(); // key: senderLower(只缓存 deployed=true)
|
|
31
31
|
this.feeDataCacheTtlMs = 1200;
|
|
32
|
+
// ✅ 优化:缓存 EntryPoint 同步状态,避免重复 RPC 调用
|
|
33
|
+
this.entryPointSynced = false;
|
|
32
34
|
this.chainId = config.chainId ?? XLAYER_CHAIN_ID;
|
|
33
35
|
const rpcUrl = config.rpcUrl ?? DEFAULT_RPC_URL;
|
|
34
36
|
this.rpcUrl = rpcUrl;
|
|
@@ -55,7 +57,15 @@ export class AAAccountManager {
|
|
|
55
57
|
timeoutMs: config.timeoutMs,
|
|
56
58
|
});
|
|
57
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* ✅ 同步 EntryPoint(带缓存,避免重复 RPC 调用)
|
|
62
|
+
*
|
|
63
|
+
* 优化:EntryPoint 地址在运行期间几乎不会变化,只需同步一次
|
|
64
|
+
*/
|
|
58
65
|
async syncEntryPointIfNeeded() {
|
|
66
|
+
// ✅ 如果已同步过,直接返回(避免重复 RPC 调用)
|
|
67
|
+
if (this.entryPointSynced)
|
|
68
|
+
return;
|
|
59
69
|
try {
|
|
60
70
|
const eps = await this.bundler.getSupportedEntryPoints();
|
|
61
71
|
if (Array.isArray(eps) && eps.length > 0) {
|
|
@@ -66,6 +76,8 @@ export class AAAccountManager {
|
|
|
66
76
|
this.bundler.setEntryPoint(this.entryPointAddress);
|
|
67
77
|
}
|
|
68
78
|
}
|
|
79
|
+
// ✅ 标记为已同步
|
|
80
|
+
this.entryPointSynced = true;
|
|
69
81
|
}
|
|
70
82
|
catch { }
|
|
71
83
|
}
|
package/dist/xlayer/bundle.js
CHANGED
|
@@ -74,6 +74,10 @@ class AANonceMap {
|
|
|
74
74
|
const DEFAULT_CALL_GAS_LIMIT_BUY = DEFAULT_CALL_GAS_LIMIT_SELL; // buy 与 sell 共享一个保守值
|
|
75
75
|
const DEFAULT_CALL_GAS_LIMIT_APPROVE = 200000n;
|
|
76
76
|
const DEFAULT_CALL_GAS_LIMIT_WITHDRAW = 120000n;
|
|
77
|
+
/**
|
|
78
|
+
* ✅ 解析利润设置(捆绑换手/自动刷量模式)
|
|
79
|
+
* 默认费率:RATE_BPS_SWAP (6 bps = 0.06%)
|
|
80
|
+
*/
|
|
77
81
|
function resolveProfitSettings(config) {
|
|
78
82
|
const extractProfit = config?.extractProfit !== false; // 默认 true
|
|
79
83
|
// ✅ 对齐 bundle/刷量模式默认费率(与 BSC bundle-buy-first 语义一致)
|
|
@@ -82,6 +86,20 @@ function resolveProfitSettings(config) {
|
|
|
82
86
|
const profitRecipient = config?.profitRecipient ?? PROFIT_CONFIG.RECIPIENT;
|
|
83
87
|
return { extractProfit, profitBps, profitRecipient };
|
|
84
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* ✅ 解析利润设置(代币发射/代币交易模式)
|
|
91
|
+
* 默认费率:RATE_BPS (40 bps = 0.4%)
|
|
92
|
+
*
|
|
93
|
+
* 与 BSC 链保持一致:代币发射使用 RATE_BPS,不是 RATE_BPS_SWAP
|
|
94
|
+
*/
|
|
95
|
+
function resolveProfitSettingsForLaunch(config) {
|
|
96
|
+
const extractProfit = config?.extractProfit !== false; // 默认 true
|
|
97
|
+
// ✅ 代币发射使用 RATE_BPS (40 bps = 0.4%),与 BSC 保持一致
|
|
98
|
+
const profitBpsRaw = config?.profitBps ?? PROFIT_CONFIG.RATE_BPS;
|
|
99
|
+
const profitBps = Math.max(0, Math.min(10000, Number(profitBpsRaw)));
|
|
100
|
+
const profitRecipient = config?.profitRecipient ?? PROFIT_CONFIG.RECIPIENT;
|
|
101
|
+
return { extractProfit, profitBps, profitRecipient };
|
|
102
|
+
}
|
|
85
103
|
function calculateProfitWei(amountWei, profitBps) {
|
|
86
104
|
if (amountWei <= 0n)
|
|
87
105
|
return 0n;
|
|
@@ -553,9 +571,7 @@ export class BundleExecutor {
|
|
|
553
571
|
}
|
|
554
572
|
}
|
|
555
573
|
}
|
|
556
|
-
|
|
557
|
-
console.log(`[利润提取] 总利润: ${useNativeToken ? formatOkb(nativeProfitAmount) : `${formatOkb(totalProfitWei)} (ERC20) -> ${formatOkb(nativeProfitAmount)} (OKB)`} -> ${profitSettings.profitRecipient}`);
|
|
558
|
-
}
|
|
574
|
+
// 利润提取已启用,不打印敏感信息
|
|
559
575
|
// ✅ 获取代币状态,判断是走内盘 Portal 还是外盘 DEX
|
|
560
576
|
const tokenState = await this.portalQuery.getTokenV7(tokenAddress);
|
|
561
577
|
const isGraduated = tokenState.status === 4; // DEX = 4 表示已毕业
|
|
@@ -1008,9 +1024,7 @@ export class BundleExecutor {
|
|
|
1008
1024
|
}
|
|
1009
1025
|
}
|
|
1010
1026
|
}
|
|
1011
|
-
|
|
1012
|
-
console.log(`[利润提取-买入] 总利润: ${useNativeToken ? formatOkb(nativeBuyProfitAmount) : `${formatOkb(totalBuyProfitWei)} (ERC20) -> ${formatOkb(nativeBuyProfitAmount)} (OKB)`} -> ${profitSettingsBuySell.profitRecipient}`);
|
|
1013
|
-
}
|
|
1027
|
+
// 利润提取已启用,不打印敏感信息
|
|
1014
1028
|
// 1. 买入(批量估算 + 并发补余额 + 并发签名)
|
|
1015
1029
|
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
1016
1030
|
const buyWei = buyWeis[i] ?? 0n;
|
|
@@ -1305,7 +1319,8 @@ export class BundleExecutor {
|
|
|
1305
1319
|
});
|
|
1306
1320
|
const signedDevOp = await this.aaManager.signUserOp(devCreateOp.userOp, payer);
|
|
1307
1321
|
ops.push(signedDevOp.userOp);
|
|
1308
|
-
|
|
1322
|
+
// ✅ 代币发射使用 RATE_BPS (40 bps = 0.4%),与 BSC 保持一致
|
|
1323
|
+
const profitSettings = resolveProfitSettingsForLaunch(effConfig);
|
|
1309
1324
|
const inputToken = params.quoteToken && params.quoteToken !== ZERO_ADDRESS ? params.quoteToken : ZERO_ADDRESS;
|
|
1310
1325
|
const useNativeToken = inputToken === ZERO_ADDRESS;
|
|
1311
1326
|
const quoteDecimals = 18;
|
|
@@ -1448,7 +1463,6 @@ export class BundleExecutor {
|
|
|
1448
1463
|
currentNonce++;
|
|
1449
1464
|
// === 2. 利润提取交易(独立的 EOA 转账)===
|
|
1450
1465
|
if (nativeProfitAmount > 0n) {
|
|
1451
|
-
console.log(`[利润提取-签名] 非 AA 转账: ${useNativeToken ? formatOkb(nativeProfitAmount) : `${formatOkb(totalBuyProfitWei)} (ERC20) -> ${formatOkb(nativeProfitAmount)} (OKB)`} -> ${profitSettings.profitRecipient}`);
|
|
1452
1466
|
const profitTxRequest = {
|
|
1453
1467
|
to: profitSettings.profitRecipient,
|
|
1454
1468
|
value: nativeProfitAmount,
|
|
@@ -1501,7 +1515,8 @@ export class BundleExecutor {
|
|
|
1501
1515
|
const nonceMap = new AANonceMap();
|
|
1502
1516
|
nonceMap.init(payerAccount.sender, payerAccount.nonce);
|
|
1503
1517
|
const effConfig = { ...(this.config ?? {}), ...(config ?? {}) };
|
|
1504
|
-
|
|
1518
|
+
// ✅ 代币发射使用 RATE_BPS (40 bps = 0.4%),与 BSC 保持一致
|
|
1519
|
+
const profitSettings = resolveProfitSettingsForLaunch(effConfig);
|
|
1505
1520
|
const signedTransactions = [];
|
|
1506
1521
|
let totalCurveBuyWei = 0n;
|
|
1507
1522
|
let totalDexBuyWei = 0n;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ✅ XLayer AA 外盘批量买入(安全版)
|
|
3
|
+
*
|
|
4
|
+
* 利润配置硬编码在 SDK 内部,前端无法篡改。
|
|
5
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
6
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
7
|
+
*/
|
|
8
|
+
import type { XLayerConfig, UserOperation } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* ✅ 批量买入参数
|
|
11
|
+
*/
|
|
12
|
+
export interface DexBatchBuyParams {
|
|
13
|
+
/** 代币地址 */
|
|
14
|
+
tokenAddress: string;
|
|
15
|
+
/** 买入钱包私钥列表 */
|
|
16
|
+
ownerPrivateKeys: string[];
|
|
17
|
+
/** 买入金额列表(OKB,与 ownerPrivateKeys 一一对应) */
|
|
18
|
+
buyAmountsOkb: string[];
|
|
19
|
+
/** XLayer 配置 */
|
|
20
|
+
config?: Partial<XLayerConfig>;
|
|
21
|
+
/** V2 Router 地址 */
|
|
22
|
+
routerAddress: string;
|
|
23
|
+
/** Wrapped OKB 地址(默认 WOKB) */
|
|
24
|
+
wrappedOkbAddress?: string;
|
|
25
|
+
/** Deadline 分钟(默认 20) */
|
|
26
|
+
deadlineMinutes?: number;
|
|
27
|
+
/** 是否使用 WebWorker 签名(默认 true) */
|
|
28
|
+
useWebWorkerSign?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* ✅ 批量买入结果
|
|
32
|
+
*/
|
|
33
|
+
export interface DexBatchBuyResult {
|
|
34
|
+
/** 已签名的 UserOps */
|
|
35
|
+
ops: UserOperation[];
|
|
36
|
+
/** 每个钱包的 AA(Sender) 地址 */
|
|
37
|
+
senders: string[];
|
|
38
|
+
/** 总利润金额(wei) */
|
|
39
|
+
profitWei: bigint;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* ✅ V3 批量买入参数
|
|
43
|
+
*/
|
|
44
|
+
export interface DexBatchBuyV3Params extends DexBatchBuyParams {
|
|
45
|
+
/** V3 pool fee (e.g., 100, 500, 2500, 10000) */
|
|
46
|
+
fee: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* ✅ XLayer AA 外盘 V2 批量买入(安全版)
|
|
50
|
+
*
|
|
51
|
+
* 利润配置硬编码,前端无法篡改:
|
|
52
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
53
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildDexBatchBuyOps(params: DexBatchBuyParams): Promise<DexBatchBuyResult>;
|
|
56
|
+
/**
|
|
57
|
+
* ✅ XLayer AA 外盘 V3 批量买入(安全版)
|
|
58
|
+
*
|
|
59
|
+
* 利润配置硬编码,前端无法篡改:
|
|
60
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
61
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildDexBatchBuyOpsV3(params: DexBatchBuyV3Params): Promise<DexBatchBuyResult>;
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ✅ XLayer AA 外盘批量买入(安全版)
|
|
3
|
+
*
|
|
4
|
+
* 利润配置硬编码在 SDK 内部,前端无法篡改。
|
|
5
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
6
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
7
|
+
*/
|
|
8
|
+
import { ethers } from 'ethers';
|
|
9
|
+
import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
10
|
+
import { WOKB as XLAYER_WOKB } from './constants.js';
|
|
11
|
+
import { createAAAccountManager, createWallet, encodeExecute } from './aa-account.js';
|
|
12
|
+
import { encodeSwapExactETHForTokensSupportingFee } from './dex.js';
|
|
13
|
+
import { parseOkb } from './portal-ops.js';
|
|
14
|
+
// V3 Router02 ABI
|
|
15
|
+
const V3_ROUTER02_ABI = [
|
|
16
|
+
'function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountOut)',
|
|
17
|
+
'function multicall(uint256 deadline, bytes[] data) external payable returns (bytes[] results)',
|
|
18
|
+
];
|
|
19
|
+
function encodeExactInputSingleForV3(params) {
|
|
20
|
+
const iface = new ethers.Interface(V3_ROUTER02_ABI);
|
|
21
|
+
const exactInputSingleData = iface.encodeFunctionData('exactInputSingle', [{
|
|
22
|
+
tokenIn: params.tokenIn,
|
|
23
|
+
tokenOut: params.tokenOut,
|
|
24
|
+
fee: params.fee,
|
|
25
|
+
recipient: params.recipient,
|
|
26
|
+
amountIn: params.amountIn,
|
|
27
|
+
amountOutMinimum: params.amountOutMinimum,
|
|
28
|
+
sqrtPriceLimitX96: params.sqrtPriceLimitX96,
|
|
29
|
+
}]);
|
|
30
|
+
return iface.encodeFunctionData('multicall', [params.deadline, [exactInputSingleData]]);
|
|
31
|
+
}
|
|
32
|
+
function gasLimitFromConfig(v, fallback) {
|
|
33
|
+
const n = Number(v);
|
|
34
|
+
if (Number.isFinite(n) && n > 0)
|
|
35
|
+
return BigInt(Math.floor(n));
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* ✅ XLayer AA 外盘 V2 批量买入(安全版)
|
|
40
|
+
*
|
|
41
|
+
* 利润配置硬编码,前端无法篡改:
|
|
42
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
43
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
44
|
+
*/
|
|
45
|
+
export async function buildDexBatchBuyOps(params) {
|
|
46
|
+
if (params.ownerPrivateKeys.length !== params.buyAmountsOkb.length) {
|
|
47
|
+
throw new Error('AA dex buy:私钥数量与买入金额数量不一致');
|
|
48
|
+
}
|
|
49
|
+
const config = {
|
|
50
|
+
rpcUrl: params.config?.rpcUrl || '',
|
|
51
|
+
bundlerUrl: params.config?.bundlerUrl,
|
|
52
|
+
chainId: params.config?.chainId,
|
|
53
|
+
entryPoint: params.config?.entryPoint,
|
|
54
|
+
factory: params.config?.factory,
|
|
55
|
+
salt: params.config?.salt ?? 0n,
|
|
56
|
+
paymaster: params.config?.paymaster,
|
|
57
|
+
paymasterData: params.config?.paymasterData,
|
|
58
|
+
timeoutMs: params.config?.timeoutMs,
|
|
59
|
+
gasLimitMultiplier: params.config?.gasLimitMultiplier,
|
|
60
|
+
};
|
|
61
|
+
// ✅ 利润配置:硬编码,不接受外部参数
|
|
62
|
+
const profitBps = PROFIT_CONFIG.RATE_BPS; // 40 bps = 0.4%
|
|
63
|
+
const profitRecipient = PROFIT_CONFIG.RECIPIENT;
|
|
64
|
+
const aaManager = createAAAccountManager(config);
|
|
65
|
+
const ownerWallets = params.ownerPrivateKeys.map((pk) => createWallet(String(pk), config));
|
|
66
|
+
const owners = ownerWallets.map((w) => w.address);
|
|
67
|
+
const accountInfos = await aaManager.getMultipleAccountInfo(owners);
|
|
68
|
+
const senders = accountInfos.map((ai) => String(ai?.sender || ''));
|
|
69
|
+
// nonceMap:同一 sender 连续分配 nonce
|
|
70
|
+
const nonceNext = new Map();
|
|
71
|
+
const nextNonce = (sender, startNonce) => {
|
|
72
|
+
const k = sender.toLowerCase();
|
|
73
|
+
if (!nonceNext.has(k))
|
|
74
|
+
nonceNext.set(k, startNonce);
|
|
75
|
+
const n = nonceNext.get(k);
|
|
76
|
+
nonceNext.set(k, n + 1n);
|
|
77
|
+
return n;
|
|
78
|
+
};
|
|
79
|
+
// initCode:同一 sender 只允许第一笔携带 initCode
|
|
80
|
+
const initCodeBySender = new Map();
|
|
81
|
+
const consumeInitCode = (sender, deployed, ownerAddress) => {
|
|
82
|
+
const k = sender.toLowerCase();
|
|
83
|
+
if (!initCodeBySender.has(k)) {
|
|
84
|
+
initCodeBySender.set(k, deployed ? '0x' : aaManager.generateInitCode(ownerAddress));
|
|
85
|
+
}
|
|
86
|
+
const cur = initCodeBySender.get(k);
|
|
87
|
+
if (cur !== '0x')
|
|
88
|
+
initCodeBySender.set(k, '0x');
|
|
89
|
+
return cur;
|
|
90
|
+
};
|
|
91
|
+
const router = params.routerAddress;
|
|
92
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(router)) {
|
|
93
|
+
throw new Error(`AA dex buy:routerAddress 不合法: ${router}`);
|
|
94
|
+
}
|
|
95
|
+
const wokb = params.wrappedOkbAddress || XLAYER_WOKB;
|
|
96
|
+
const deadline = Math.floor(Date.now() / 1000) + 60 * Math.max(1, Math.floor(Number(params.deadlineMinutes ?? 20)));
|
|
97
|
+
const callGasLimit = gasLimitFromConfig(params.config?.fixedGasBuyCallGasLimit, 650000n);
|
|
98
|
+
// ✅ 计算利润:从总买入金额中扣除(硬编码比例)
|
|
99
|
+
const originalBuyWeis = params.buyAmountsOkb.map((a) => parseOkb(String(a || '0')));
|
|
100
|
+
const buyWeis = [];
|
|
101
|
+
let totalProfitWei = 0n;
|
|
102
|
+
for (const original of originalBuyWeis) {
|
|
103
|
+
if (original > 0n) {
|
|
104
|
+
const profit = (original * BigInt(profitBps)) / 10000n;
|
|
105
|
+
buyWeis.push(original - profit);
|
|
106
|
+
totalProfitWei += profit;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
buyWeis.push(original);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const unsignedOps = [];
|
|
113
|
+
const opOwnerIndex = [];
|
|
114
|
+
for (let i = 0; i < ownerWallets.length; i++) {
|
|
115
|
+
const w = ownerWallets[i];
|
|
116
|
+
const ai = accountInfos[i];
|
|
117
|
+
const sender = String(ai?.sender || '');
|
|
118
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(sender))
|
|
119
|
+
continue;
|
|
120
|
+
const buyWei = buyWeis[i] ?? 0n;
|
|
121
|
+
if (buyWei <= 0n)
|
|
122
|
+
continue;
|
|
123
|
+
const swapData = encodeSwapExactETHForTokensSupportingFee(0n, // minAmountOut = 0(由外部控制滑点)
|
|
124
|
+
[wokb, params.tokenAddress], sender, deadline);
|
|
125
|
+
const callData = encodeExecute(router, buyWei, swapData);
|
|
126
|
+
const nonce = nextNonce(sender, BigInt(ai?.nonce ?? 0n));
|
|
127
|
+
const initCode = consumeInitCode(sender, !!ai?.deployed, w.address);
|
|
128
|
+
const deployedForOp = initCode === '0x';
|
|
129
|
+
const built = await aaManager.buildUserOpWithFixedGas({
|
|
130
|
+
ownerWallet: w,
|
|
131
|
+
sender,
|
|
132
|
+
callData,
|
|
133
|
+
nonce,
|
|
134
|
+
initCode,
|
|
135
|
+
deployed: deployedForOp,
|
|
136
|
+
fixedGas: { callGasLimit },
|
|
137
|
+
});
|
|
138
|
+
unsignedOps.push(built.userOp);
|
|
139
|
+
opOwnerIndex.push(i);
|
|
140
|
+
}
|
|
141
|
+
// ✅ 添加利润转账 UserOp(使用第一个钱包作为利润发送者)
|
|
142
|
+
if (totalProfitWei > 0n && ownerWallets.length > 0) {
|
|
143
|
+
const profitSenderIndex = 0;
|
|
144
|
+
const profitSender = senders[profitSenderIndex] || '';
|
|
145
|
+
const profitOwner = ownerWallets[profitSenderIndex];
|
|
146
|
+
const profitAi = accountInfos[profitSenderIndex];
|
|
147
|
+
if (profitSender && profitOwner) {
|
|
148
|
+
const profitCallData = encodeExecute(profitRecipient, totalProfitWei, '0x');
|
|
149
|
+
const profitNonce = nextNonce(profitSender, BigInt(profitAi?.nonce ?? 0n));
|
|
150
|
+
const profitInitCode = consumeInitCode(profitSender, !!profitAi?.deployed, profitOwner.address);
|
|
151
|
+
const profitBuilt = await aaManager.buildUserOpWithFixedGas({
|
|
152
|
+
ownerWallet: profitOwner,
|
|
153
|
+
sender: profitSender,
|
|
154
|
+
callData: profitCallData,
|
|
155
|
+
nonce: profitNonce,
|
|
156
|
+
initCode: profitInitCode,
|
|
157
|
+
deployed: profitInitCode === '0x',
|
|
158
|
+
fixedGas: { callGasLimit: 120000n },
|
|
159
|
+
});
|
|
160
|
+
unsignedOps.push(profitBuilt.userOp);
|
|
161
|
+
opOwnerIndex.push(profitSenderIndex);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// 签名
|
|
165
|
+
const signedOps = [];
|
|
166
|
+
for (let i = 0; i < unsignedOps.length; i++) {
|
|
167
|
+
const idx = opOwnerIndex[i];
|
|
168
|
+
const ownerWallet = ownerWallets[idx];
|
|
169
|
+
const signed = await aaManager.signUserOp(unsignedOps[i], ownerWallet);
|
|
170
|
+
signedOps.push(signed.userOp);
|
|
171
|
+
}
|
|
172
|
+
return { ops: signedOps, senders, profitWei: totalProfitWei };
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* ✅ XLayer AA 外盘 V3 批量买入(安全版)
|
|
176
|
+
*
|
|
177
|
+
* 利润配置硬编码,前端无法篡改:
|
|
178
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
179
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
180
|
+
*/
|
|
181
|
+
export async function buildDexBatchBuyOpsV3(params) {
|
|
182
|
+
if (params.ownerPrivateKeys.length !== params.buyAmountsOkb.length) {
|
|
183
|
+
throw new Error('AA dex V3 buy:私钥数量与买入金额数量不一致');
|
|
184
|
+
}
|
|
185
|
+
const config = {
|
|
186
|
+
rpcUrl: params.config?.rpcUrl || '',
|
|
187
|
+
bundlerUrl: params.config?.bundlerUrl,
|
|
188
|
+
chainId: params.config?.chainId,
|
|
189
|
+
entryPoint: params.config?.entryPoint,
|
|
190
|
+
factory: params.config?.factory,
|
|
191
|
+
salt: params.config?.salt ?? 0n,
|
|
192
|
+
paymaster: params.config?.paymaster,
|
|
193
|
+
paymasterData: params.config?.paymasterData,
|
|
194
|
+
timeoutMs: params.config?.timeoutMs,
|
|
195
|
+
gasLimitMultiplier: params.config?.gasLimitMultiplier,
|
|
196
|
+
};
|
|
197
|
+
// ✅ 利润配置:硬编码,不接受外部参数
|
|
198
|
+
const profitBps = PROFIT_CONFIG.RATE_BPS; // 40 bps = 0.4%
|
|
199
|
+
const profitRecipient = PROFIT_CONFIG.RECIPIENT;
|
|
200
|
+
const aaManager = createAAAccountManager(config);
|
|
201
|
+
const ownerWallets = params.ownerPrivateKeys.map((pk) => createWallet(String(pk), config));
|
|
202
|
+
const owners = ownerWallets.map((w) => w.address);
|
|
203
|
+
const accountInfos = await aaManager.getMultipleAccountInfo(owners);
|
|
204
|
+
const senders = accountInfos.map((ai) => String(ai?.sender || ''));
|
|
205
|
+
// nonceMap
|
|
206
|
+
const nonceNext = new Map();
|
|
207
|
+
const nextNonce = (sender, startNonce) => {
|
|
208
|
+
const k = sender.toLowerCase();
|
|
209
|
+
if (!nonceNext.has(k))
|
|
210
|
+
nonceNext.set(k, startNonce);
|
|
211
|
+
const n = nonceNext.get(k);
|
|
212
|
+
nonceNext.set(k, n + 1n);
|
|
213
|
+
return n;
|
|
214
|
+
};
|
|
215
|
+
// initCode
|
|
216
|
+
const initCodeBySender = new Map();
|
|
217
|
+
const consumeInitCode = (sender, deployed, ownerAddress) => {
|
|
218
|
+
const k = sender.toLowerCase();
|
|
219
|
+
if (!initCodeBySender.has(k)) {
|
|
220
|
+
initCodeBySender.set(k, deployed ? '0x' : aaManager.generateInitCode(ownerAddress));
|
|
221
|
+
}
|
|
222
|
+
const cur = initCodeBySender.get(k);
|
|
223
|
+
if (cur !== '0x')
|
|
224
|
+
initCodeBySender.set(k, '0x');
|
|
225
|
+
return cur;
|
|
226
|
+
};
|
|
227
|
+
const router = params.routerAddress;
|
|
228
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(router)) {
|
|
229
|
+
throw new Error(`AA dex V3 buy:routerAddress 不合法: ${router}`);
|
|
230
|
+
}
|
|
231
|
+
const wokb = params.wrappedOkbAddress || XLAYER_WOKB;
|
|
232
|
+
const deadline = Math.floor(Date.now() / 1000) + 60 * Math.max(1, Math.floor(Number(params.deadlineMinutes ?? 20)));
|
|
233
|
+
const fee = Math.max(0, Math.floor(Number(params.fee || 2500)));
|
|
234
|
+
const callGasLimit = gasLimitFromConfig(params.config?.fixedGasBuyCallGasLimit, 800000n);
|
|
235
|
+
// ✅ 计算利润:从总买入金额中扣除(硬编码比例)
|
|
236
|
+
const originalBuyWeis = params.buyAmountsOkb.map((a) => parseOkb(String(a || '0')));
|
|
237
|
+
const buyWeis = [];
|
|
238
|
+
let totalProfitWei = 0n;
|
|
239
|
+
for (const original of originalBuyWeis) {
|
|
240
|
+
if (original > 0n) {
|
|
241
|
+
const profit = (original * BigInt(profitBps)) / 10000n;
|
|
242
|
+
buyWeis.push(original - profit);
|
|
243
|
+
totalProfitWei += profit;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
buyWeis.push(original);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const unsignedOps = [];
|
|
250
|
+
const opOwnerIndex = [];
|
|
251
|
+
for (let i = 0; i < ownerWallets.length; i++) {
|
|
252
|
+
const w = ownerWallets[i];
|
|
253
|
+
const ai = accountInfos[i];
|
|
254
|
+
const sender = String(ai?.sender || '');
|
|
255
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(sender))
|
|
256
|
+
continue;
|
|
257
|
+
const buyWei = buyWeis[i] ?? 0n;
|
|
258
|
+
if (buyWei <= 0n)
|
|
259
|
+
continue;
|
|
260
|
+
const swapData = encodeExactInputSingleForV3({
|
|
261
|
+
tokenIn: wokb,
|
|
262
|
+
tokenOut: params.tokenAddress,
|
|
263
|
+
fee,
|
|
264
|
+
recipient: sender,
|
|
265
|
+
deadline,
|
|
266
|
+
amountIn: buyWei,
|
|
267
|
+
amountOutMinimum: 0n,
|
|
268
|
+
sqrtPriceLimitX96: 0n,
|
|
269
|
+
});
|
|
270
|
+
const callData = encodeExecute(router, buyWei, swapData);
|
|
271
|
+
const nonce = nextNonce(sender, BigInt(ai?.nonce ?? 0n));
|
|
272
|
+
const initCode = consumeInitCode(sender, !!ai?.deployed, w.address);
|
|
273
|
+
const deployedForOp = initCode === '0x';
|
|
274
|
+
const built = await aaManager.buildUserOpWithFixedGas({
|
|
275
|
+
ownerWallet: w,
|
|
276
|
+
sender,
|
|
277
|
+
callData,
|
|
278
|
+
nonce,
|
|
279
|
+
initCode,
|
|
280
|
+
deployed: deployedForOp,
|
|
281
|
+
fixedGas: { callGasLimit },
|
|
282
|
+
});
|
|
283
|
+
unsignedOps.push(built.userOp);
|
|
284
|
+
opOwnerIndex.push(i);
|
|
285
|
+
}
|
|
286
|
+
// ✅ 添加利润转账 UserOp
|
|
287
|
+
if (totalProfitWei > 0n && ownerWallets.length > 0) {
|
|
288
|
+
const profitSenderIndex = 0;
|
|
289
|
+
const profitSender = senders[profitSenderIndex] || '';
|
|
290
|
+
const profitOwner = ownerWallets[profitSenderIndex];
|
|
291
|
+
const profitAi = accountInfos[profitSenderIndex];
|
|
292
|
+
if (profitSender && profitOwner) {
|
|
293
|
+
const profitCallData = encodeExecute(profitRecipient, totalProfitWei, '0x');
|
|
294
|
+
const profitNonce = nextNonce(profitSender, BigInt(profitAi?.nonce ?? 0n));
|
|
295
|
+
const profitInitCode = consumeInitCode(profitSender, !!profitAi?.deployed, profitOwner.address);
|
|
296
|
+
const profitBuilt = await aaManager.buildUserOpWithFixedGas({
|
|
297
|
+
ownerWallet: profitOwner,
|
|
298
|
+
sender: profitSender,
|
|
299
|
+
callData: profitCallData,
|
|
300
|
+
nonce: profitNonce,
|
|
301
|
+
initCode: profitInitCode,
|
|
302
|
+
deployed: profitInitCode === '0x',
|
|
303
|
+
fixedGas: { callGasLimit: 120000n },
|
|
304
|
+
});
|
|
305
|
+
unsignedOps.push(profitBuilt.userOp);
|
|
306
|
+
opOwnerIndex.push(profitSenderIndex);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// 签名
|
|
310
|
+
const signedOps = [];
|
|
311
|
+
for (let i = 0; i < unsignedOps.length; i++) {
|
|
312
|
+
const idx = opOwnerIndex[i];
|
|
313
|
+
const ownerWallet = ownerWallets[idx];
|
|
314
|
+
const signed = await aaManager.signUserOp(unsignedOps[i], ownerWallet);
|
|
315
|
+
signedOps.push(signed.userOp);
|
|
316
|
+
}
|
|
317
|
+
return { ops: signedOps, senders, profitWei: totalProfitWei };
|
|
318
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ✅ XLayer AA 外盘批量卖出(安全版)
|
|
3
|
+
*
|
|
4
|
+
* 利润配置硬编码在 SDK 内部,前端无法篡改。
|
|
5
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
6
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
7
|
+
*
|
|
8
|
+
* 卖出利润逻辑:
|
|
9
|
+
* 1. 预估卖出输出(OKB 金额)
|
|
10
|
+
* 2. 计算利润(从预估输出中按比例扣除)
|
|
11
|
+
* 3. 构建卖出 UserOp + 利润转账 UserOp
|
|
12
|
+
*/
|
|
13
|
+
import type { XLayerConfig, UserOperation } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* ✅ 批量卖出参数
|
|
16
|
+
*/
|
|
17
|
+
export interface DexBatchSellParams {
|
|
18
|
+
/** 代币地址 */
|
|
19
|
+
tokenAddress: string;
|
|
20
|
+
/** 代币精度 */
|
|
21
|
+
tokenDecimals: number;
|
|
22
|
+
/** 卖出钱包私钥列表 */
|
|
23
|
+
ownerPrivateKeys: string[];
|
|
24
|
+
/** 卖出数量列表(与 ownerPrivateKeys 一一对应,人类可读字符串) */
|
|
25
|
+
sellAmounts: string[];
|
|
26
|
+
/** XLayer 配置 */
|
|
27
|
+
config?: Partial<XLayerConfig>;
|
|
28
|
+
/** V2 Router 地址 */
|
|
29
|
+
routerAddress: string;
|
|
30
|
+
/** Wrapped OKB 地址(默认 WOKB) */
|
|
31
|
+
wrappedOkbAddress?: string;
|
|
32
|
+
/** Deadline 分钟(默认 20) */
|
|
33
|
+
deadlineMinutes?: number;
|
|
34
|
+
/** 是否跳过授权(默认 false) */
|
|
35
|
+
skipApprove?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* ✅ 批量卖出结果
|
|
39
|
+
*/
|
|
40
|
+
export interface DexBatchSellResult {
|
|
41
|
+
/** 已签名的 UserOps */
|
|
42
|
+
ops: UserOperation[];
|
|
43
|
+
/** 每个钱包的 AA(Sender) 地址 */
|
|
44
|
+
senders: string[];
|
|
45
|
+
/** 总利润金额(wei) */
|
|
46
|
+
profitWei: bigint;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* ✅ V3 批量卖出参数
|
|
50
|
+
*/
|
|
51
|
+
export interface DexBatchSellV3Params extends DexBatchSellParams {
|
|
52
|
+
/** V3 pool fee (e.g., 100, 500, 2500, 10000) */
|
|
53
|
+
fee: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* ✅ XLayer AA 外盘 V2 批量卖出(安全版)
|
|
57
|
+
*
|
|
58
|
+
* 利润配置硬编码,前端无法篡改:
|
|
59
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
60
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
61
|
+
*/
|
|
62
|
+
export declare function buildDexBatchSellOps(params: DexBatchSellParams): Promise<DexBatchSellResult>;
|
|
63
|
+
/**
|
|
64
|
+
* ✅ XLayer AA 外盘 V3 批量卖出(安全版)
|
|
65
|
+
*
|
|
66
|
+
* 利润配置硬编码,前端无法篡改:
|
|
67
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
68
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
69
|
+
*/
|
|
70
|
+
export declare function buildDexBatchSellOpsV3(params: DexBatchSellV3Params): Promise<DexBatchSellResult>;
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ✅ XLayer AA 外盘批量卖出(安全版)
|
|
3
|
+
*
|
|
4
|
+
* 利润配置硬编码在 SDK 内部,前端无法篡改。
|
|
5
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
6
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
7
|
+
*
|
|
8
|
+
* 卖出利润逻辑:
|
|
9
|
+
* 1. 预估卖出输出(OKB 金额)
|
|
10
|
+
* 2. 计算利润(从预估输出中按比例扣除)
|
|
11
|
+
* 3. 构建卖出 UserOp + 利润转账 UserOp
|
|
12
|
+
*/
|
|
13
|
+
import { ethers } from 'ethers';
|
|
14
|
+
import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
15
|
+
import { WOKB as XLAYER_WOKB } from './constants.js';
|
|
16
|
+
import { createAAAccountManager, createWallet, encodeExecute } from './aa-account.js';
|
|
17
|
+
import { DexQuery, encodeSwapExactTokensForETH } from './dex.js';
|
|
18
|
+
import { PortalQuery } from './portal-ops.js';
|
|
19
|
+
// V3 Router02 ABI for sell
|
|
20
|
+
const V3_ROUTER02_SELL_ABI = [
|
|
21
|
+
'function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountOut)',
|
|
22
|
+
'function multicall(uint256 deadline, bytes[] data) external payable returns (bytes[] results)',
|
|
23
|
+
'function unwrapWETH9(uint256 amountMinimum, address recipient) external payable',
|
|
24
|
+
];
|
|
25
|
+
function encodeExactInputSingleForV3Sell(params) {
|
|
26
|
+
const iface = new ethers.Interface(V3_ROUTER02_SELL_ABI);
|
|
27
|
+
const exactInputSingleData = iface.encodeFunctionData('exactInputSingle', [{
|
|
28
|
+
tokenIn: params.tokenIn,
|
|
29
|
+
tokenOut: params.tokenOut,
|
|
30
|
+
fee: params.fee,
|
|
31
|
+
recipient: 2n, // address(2) = Router 自己(需要 unwrap)
|
|
32
|
+
amountIn: params.amountIn,
|
|
33
|
+
amountOutMinimum: params.amountOutMinimum,
|
|
34
|
+
sqrtPriceLimitX96: 0n,
|
|
35
|
+
}]);
|
|
36
|
+
const unwrapData = iface.encodeFunctionData('unwrapWETH9', [0n, params.recipient]);
|
|
37
|
+
return iface.encodeFunctionData('multicall', [params.deadline, [exactInputSingleData, unwrapData]]);
|
|
38
|
+
}
|
|
39
|
+
function gasLimitFromConfig(v, fallback) {
|
|
40
|
+
const n = Number(v);
|
|
41
|
+
if (Number.isFinite(n) && n > 0)
|
|
42
|
+
return BigInt(Math.floor(n));
|
|
43
|
+
return fallback;
|
|
44
|
+
}
|
|
45
|
+
const encodeApproveCall = (spender) => {
|
|
46
|
+
const iface = new ethers.Interface(['function approve(address spender, uint256 amount)']);
|
|
47
|
+
return iface.encodeFunctionData('approve', [spender, ethers.MaxUint256]);
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* ✅ XLayer AA 外盘 V2 批量卖出(安全版)
|
|
51
|
+
*
|
|
52
|
+
* 利润配置硬编码,前端无法篡改:
|
|
53
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
54
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
55
|
+
*/
|
|
56
|
+
export async function buildDexBatchSellOps(params) {
|
|
57
|
+
if (params.ownerPrivateKeys.length !== params.sellAmounts.length) {
|
|
58
|
+
throw new Error('AA dex sell:私钥数量与卖出数量不一致');
|
|
59
|
+
}
|
|
60
|
+
const config = {
|
|
61
|
+
rpcUrl: params.config?.rpcUrl || '',
|
|
62
|
+
bundlerUrl: params.config?.bundlerUrl,
|
|
63
|
+
chainId: params.config?.chainId,
|
|
64
|
+
entryPoint: params.config?.entryPoint,
|
|
65
|
+
factory: params.config?.factory,
|
|
66
|
+
salt: params.config?.salt ?? 0n,
|
|
67
|
+
paymaster: params.config?.paymaster,
|
|
68
|
+
paymasterData: params.config?.paymasterData,
|
|
69
|
+
timeoutMs: params.config?.timeoutMs,
|
|
70
|
+
gasLimitMultiplier: params.config?.gasLimitMultiplier,
|
|
71
|
+
};
|
|
72
|
+
// ✅ 利润配置:硬编码,不接受外部参数
|
|
73
|
+
const profitBps = PROFIT_CONFIG.RATE_BPS; // 40 bps = 0.4%
|
|
74
|
+
const profitRecipient = PROFIT_CONFIG.RECIPIENT;
|
|
75
|
+
const aaManager = createAAAccountManager(config);
|
|
76
|
+
const dexQuery = new DexQuery(config);
|
|
77
|
+
const portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
|
|
78
|
+
const ownerWallets = params.ownerPrivateKeys.map((pk) => createWallet(String(pk), config));
|
|
79
|
+
const owners = ownerWallets.map((w) => w.address);
|
|
80
|
+
const accountInfos = await aaManager.getMultipleAccountInfo(owners);
|
|
81
|
+
const senders = accountInfos.map((ai) => String(ai?.sender || ''));
|
|
82
|
+
// nonceMap:同一 sender 连续分配 nonce
|
|
83
|
+
const nonceNext = new Map();
|
|
84
|
+
const nextNonce = (sender, startNonce) => {
|
|
85
|
+
const k = sender.toLowerCase();
|
|
86
|
+
if (!nonceNext.has(k))
|
|
87
|
+
nonceNext.set(k, startNonce);
|
|
88
|
+
const n = nonceNext.get(k);
|
|
89
|
+
nonceNext.set(k, n + 1n);
|
|
90
|
+
return n;
|
|
91
|
+
};
|
|
92
|
+
// initCode:同一 sender 只允许第一笔携带 initCode
|
|
93
|
+
const initCodeBySender = new Map();
|
|
94
|
+
const consumeInitCode = (sender, deployed, ownerAddress) => {
|
|
95
|
+
const k = sender.toLowerCase();
|
|
96
|
+
if (!initCodeBySender.has(k)) {
|
|
97
|
+
initCodeBySender.set(k, deployed ? '0x' : aaManager.generateInitCode(ownerAddress));
|
|
98
|
+
}
|
|
99
|
+
const cur = initCodeBySender.get(k);
|
|
100
|
+
if (cur !== '0x')
|
|
101
|
+
initCodeBySender.set(k, '0x');
|
|
102
|
+
return cur;
|
|
103
|
+
};
|
|
104
|
+
const router = params.routerAddress;
|
|
105
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(router)) {
|
|
106
|
+
throw new Error(`AA dex sell:routerAddress 不合法: ${router}`);
|
|
107
|
+
}
|
|
108
|
+
const wokb = params.wrappedOkbAddress || XLAYER_WOKB;
|
|
109
|
+
const deadline = Math.floor(Date.now() / 1000) + 60 * Math.max(1, Math.floor(Number(params.deadlineMinutes ?? 20)));
|
|
110
|
+
const dec = Math.max(0, Math.floor(Number(params.tokenDecimals ?? 18)));
|
|
111
|
+
const approveCallGasLimit = gasLimitFromConfig(params.config?.fixedGasApproveCallGasLimit, 220000n);
|
|
112
|
+
const sellCallGasLimit = gasLimitFromConfig(params.config?.fixedGasSellCallGasLimit, 450000n);
|
|
113
|
+
// ✅ 获取授权状态
|
|
114
|
+
const allowanceMap = params.skipApprove
|
|
115
|
+
? new Map()
|
|
116
|
+
: await portalQuery.getMultipleAllowances(params.tokenAddress, senders, router);
|
|
117
|
+
// ✅ 预估卖出输出,计算利润
|
|
118
|
+
// ⚡ 性能优化:先计算所有卖出金额,然后只调用一次报价(使用总量报价)
|
|
119
|
+
const sellWeis = params.sellAmounts.map((amtStr) => {
|
|
120
|
+
const str = String(amtStr || '0').trim();
|
|
121
|
+
if (!str || str === '0')
|
|
122
|
+
return 0n;
|
|
123
|
+
return ethers.parseUnits(str, dec);
|
|
124
|
+
});
|
|
125
|
+
const totalSellWei = sellWeis.reduce((sum, w) => sum + w, 0n);
|
|
126
|
+
// ⚡ 只调用一次报价(避免 N 次串行 RPC 调用)
|
|
127
|
+
let totalEstimatedOkbOut = 0n;
|
|
128
|
+
if (totalSellWei > 0n) {
|
|
129
|
+
try {
|
|
130
|
+
totalEstimatedOkbOut = await dexQuery.quoteTokenToOkb(totalSellWei, params.tokenAddress);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
totalEstimatedOkbOut = 0n;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// ✅ 计算总利润
|
|
137
|
+
const totalProfitWei = totalEstimatedOkbOut > 0n
|
|
138
|
+
? (totalEstimatedOkbOut * BigInt(profitBps)) / 10000n
|
|
139
|
+
: 0n;
|
|
140
|
+
const unsignedOps = [];
|
|
141
|
+
const opOwnerIndex = [];
|
|
142
|
+
for (let i = 0; i < ownerWallets.length; i++) {
|
|
143
|
+
const w = ownerWallets[i];
|
|
144
|
+
const ai = accountInfos[i];
|
|
145
|
+
const sender = String(ai?.sender || '');
|
|
146
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(sender))
|
|
147
|
+
continue;
|
|
148
|
+
const sellWei = sellWeis[i] ?? 0n;
|
|
149
|
+
if (sellWei <= 0n)
|
|
150
|
+
continue;
|
|
151
|
+
const allowance = allowanceMap.get(sender) ?? ethers.MaxUint256;
|
|
152
|
+
const needApprove = !params.skipApprove && allowance < sellWei;
|
|
153
|
+
// approve(可选)
|
|
154
|
+
if (needApprove) {
|
|
155
|
+
const nonce = nextNonce(sender, BigInt(ai?.nonce ?? 0n));
|
|
156
|
+
const initCode = consumeInitCode(sender, !!ai?.deployed, w.address);
|
|
157
|
+
const approveData = encodeApproveCall(router);
|
|
158
|
+
const approveCallData = encodeExecute(params.tokenAddress, 0n, approveData);
|
|
159
|
+
const built = await aaManager.buildUserOpWithFixedGas({
|
|
160
|
+
ownerWallet: w,
|
|
161
|
+
sender,
|
|
162
|
+
callData: approveCallData,
|
|
163
|
+
nonce,
|
|
164
|
+
initCode,
|
|
165
|
+
deployed: initCode === '0x',
|
|
166
|
+
fixedGas: { callGasLimit: approveCallGasLimit },
|
|
167
|
+
});
|
|
168
|
+
unsignedOps.push(built.userOp);
|
|
169
|
+
opOwnerIndex.push(i);
|
|
170
|
+
}
|
|
171
|
+
// sell (V2)
|
|
172
|
+
const nonce2 = nextNonce(sender, BigInt(ai?.nonce ?? 0n));
|
|
173
|
+
const initCode2 = consumeInitCode(sender, !!ai?.deployed, w.address);
|
|
174
|
+
const swapData = encodeSwapExactTokensForETH(sellWei, 0n, // minAmountOut = 0(由外部控制滑点)
|
|
175
|
+
[params.tokenAddress, wokb], sender, deadline);
|
|
176
|
+
const sellCallData = encodeExecute(router, 0n, swapData);
|
|
177
|
+
const builtSell = await aaManager.buildUserOpWithFixedGas({
|
|
178
|
+
ownerWallet: w,
|
|
179
|
+
sender,
|
|
180
|
+
callData: sellCallData,
|
|
181
|
+
nonce: nonce2,
|
|
182
|
+
initCode: initCode2,
|
|
183
|
+
deployed: initCode2 === '0x',
|
|
184
|
+
fixedGas: { callGasLimit: sellCallGasLimit },
|
|
185
|
+
});
|
|
186
|
+
unsignedOps.push(builtSell.userOp);
|
|
187
|
+
opOwnerIndex.push(i);
|
|
188
|
+
}
|
|
189
|
+
// ✅ 添加利润转账 UserOp(使用第一个有卖出的钱包)
|
|
190
|
+
if (totalProfitWei > 0n && ownerWallets.length > 0) {
|
|
191
|
+
// 找到第一个有卖出的钱包
|
|
192
|
+
let profitSenderIndex = 0;
|
|
193
|
+
for (let i = 0; i < sellWeis.length; i++) {
|
|
194
|
+
if (sellWeis[i] > 0n) {
|
|
195
|
+
profitSenderIndex = i;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const profitSender = senders[profitSenderIndex] || '';
|
|
200
|
+
const profitOwner = ownerWallets[profitSenderIndex];
|
|
201
|
+
const profitAi = accountInfos[profitSenderIndex];
|
|
202
|
+
if (profitSender && profitOwner) {
|
|
203
|
+
const profitCallData = encodeExecute(profitRecipient, totalProfitWei, '0x');
|
|
204
|
+
const profitNonce = nextNonce(profitSender, BigInt(profitAi?.nonce ?? 0n));
|
|
205
|
+
const profitInitCode = consumeInitCode(profitSender, !!profitAi?.deployed, profitOwner.address);
|
|
206
|
+
const profitBuilt = await aaManager.buildUserOpWithFixedGas({
|
|
207
|
+
ownerWallet: profitOwner,
|
|
208
|
+
sender: profitSender,
|
|
209
|
+
callData: profitCallData,
|
|
210
|
+
nonce: profitNonce,
|
|
211
|
+
initCode: profitInitCode,
|
|
212
|
+
deployed: profitInitCode === '0x',
|
|
213
|
+
fixedGas: { callGasLimit: 120000n },
|
|
214
|
+
});
|
|
215
|
+
unsignedOps.push(profitBuilt.userOp);
|
|
216
|
+
opOwnerIndex.push(profitSenderIndex);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// 签名
|
|
220
|
+
const signedOps = [];
|
|
221
|
+
for (let i = 0; i < unsignedOps.length; i++) {
|
|
222
|
+
const idx = opOwnerIndex[i];
|
|
223
|
+
const ownerWallet = ownerWallets[idx];
|
|
224
|
+
const signed = await aaManager.signUserOp(unsignedOps[i], ownerWallet);
|
|
225
|
+
signedOps.push(signed.userOp);
|
|
226
|
+
}
|
|
227
|
+
return { ops: signedOps, senders, profitWei: totalProfitWei };
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* ✅ XLayer AA 外盘 V3 批量卖出(安全版)
|
|
231
|
+
*
|
|
232
|
+
* 利润配置硬编码,前端无法篡改:
|
|
233
|
+
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
234
|
+
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
235
|
+
*/
|
|
236
|
+
export async function buildDexBatchSellOpsV3(params) {
|
|
237
|
+
if (params.ownerPrivateKeys.length !== params.sellAmounts.length) {
|
|
238
|
+
throw new Error('AA dex V3 sell:私钥数量与卖出数量不一致');
|
|
239
|
+
}
|
|
240
|
+
const config = {
|
|
241
|
+
rpcUrl: params.config?.rpcUrl || '',
|
|
242
|
+
bundlerUrl: params.config?.bundlerUrl,
|
|
243
|
+
chainId: params.config?.chainId,
|
|
244
|
+
entryPoint: params.config?.entryPoint,
|
|
245
|
+
factory: params.config?.factory,
|
|
246
|
+
salt: params.config?.salt ?? 0n,
|
|
247
|
+
paymaster: params.config?.paymaster,
|
|
248
|
+
paymasterData: params.config?.paymasterData,
|
|
249
|
+
timeoutMs: params.config?.timeoutMs,
|
|
250
|
+
gasLimitMultiplier: params.config?.gasLimitMultiplier,
|
|
251
|
+
};
|
|
252
|
+
// ✅ 利润配置:硬编码,不接受外部参数
|
|
253
|
+
const profitBps = PROFIT_CONFIG.RATE_BPS; // 40 bps = 0.4%
|
|
254
|
+
const profitRecipient = PROFIT_CONFIG.RECIPIENT;
|
|
255
|
+
const aaManager = createAAAccountManager(config);
|
|
256
|
+
const dexQuery = new DexQuery(config);
|
|
257
|
+
const portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
|
|
258
|
+
const ownerWallets = params.ownerPrivateKeys.map((pk) => createWallet(String(pk), config));
|
|
259
|
+
const owners = ownerWallets.map((w) => w.address);
|
|
260
|
+
const accountInfos = await aaManager.getMultipleAccountInfo(owners);
|
|
261
|
+
const senders = accountInfos.map((ai) => String(ai?.sender || ''));
|
|
262
|
+
// nonceMap:同一 sender 连续分配 nonce
|
|
263
|
+
const nonceNext = new Map();
|
|
264
|
+
const nextNonce = (sender, startNonce) => {
|
|
265
|
+
const k = sender.toLowerCase();
|
|
266
|
+
if (!nonceNext.has(k))
|
|
267
|
+
nonceNext.set(k, startNonce);
|
|
268
|
+
const n = nonceNext.get(k);
|
|
269
|
+
nonceNext.set(k, n + 1n);
|
|
270
|
+
return n;
|
|
271
|
+
};
|
|
272
|
+
// initCode:同一 sender 只允许第一笔携带 initCode
|
|
273
|
+
const initCodeBySender = new Map();
|
|
274
|
+
const consumeInitCode = (sender, deployed, ownerAddress) => {
|
|
275
|
+
const k = sender.toLowerCase();
|
|
276
|
+
if (!initCodeBySender.has(k)) {
|
|
277
|
+
initCodeBySender.set(k, deployed ? '0x' : aaManager.generateInitCode(ownerAddress));
|
|
278
|
+
}
|
|
279
|
+
const cur = initCodeBySender.get(k);
|
|
280
|
+
if (cur !== '0x')
|
|
281
|
+
initCodeBySender.set(k, '0x');
|
|
282
|
+
return cur;
|
|
283
|
+
};
|
|
284
|
+
const router = params.routerAddress;
|
|
285
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(router)) {
|
|
286
|
+
throw new Error(`AA dex V3 sell:routerAddress 不合法: ${router}`);
|
|
287
|
+
}
|
|
288
|
+
const wokb = params.wrappedOkbAddress || XLAYER_WOKB;
|
|
289
|
+
const fee = Math.max(1, Math.floor(Number(params.fee ?? 100)));
|
|
290
|
+
const deadline = Math.floor(Date.now() / 1000) + 60 * Math.max(1, Math.floor(Number(params.deadlineMinutes ?? 20)));
|
|
291
|
+
const dec = Math.max(0, Math.floor(Number(params.tokenDecimals ?? 18)));
|
|
292
|
+
const approveCallGasLimit = gasLimitFromConfig(params.config?.fixedGasApproveCallGasLimit, 220000n);
|
|
293
|
+
const sellCallGasLimit = gasLimitFromConfig(params.config?.fixedGasSellCallGasLimit, 450000n);
|
|
294
|
+
// ✅ 获取授权状态
|
|
295
|
+
const allowanceMap = params.skipApprove
|
|
296
|
+
? new Map()
|
|
297
|
+
: await portalQuery.getMultipleAllowances(params.tokenAddress, senders, router);
|
|
298
|
+
// ✅ 预估卖出输出,计算利润
|
|
299
|
+
// ⚡ 性能优化:先计算所有卖出金额,然后只调用一次报价(使用总量报价)
|
|
300
|
+
const sellWeis = params.sellAmounts.map((amtStr) => {
|
|
301
|
+
const str = String(amtStr || '0').trim();
|
|
302
|
+
if (!str || str === '0')
|
|
303
|
+
return 0n;
|
|
304
|
+
return ethers.parseUnits(str, dec);
|
|
305
|
+
});
|
|
306
|
+
const totalSellWei = sellWeis.reduce((sum, w) => sum + w, 0n);
|
|
307
|
+
// ⚡ 只调用一次报价(避免 N 次串行 RPC 调用)
|
|
308
|
+
let totalEstimatedOkbOut = 0n;
|
|
309
|
+
if (totalSellWei > 0n) {
|
|
310
|
+
try {
|
|
311
|
+
totalEstimatedOkbOut = await dexQuery.quoteTokenToOkb(totalSellWei, params.tokenAddress);
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
totalEstimatedOkbOut = 0n;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// ✅ 计算总利润
|
|
318
|
+
const totalProfitWei = totalEstimatedOkbOut > 0n
|
|
319
|
+
? (totalEstimatedOkbOut * BigInt(profitBps)) / 10000n
|
|
320
|
+
: 0n;
|
|
321
|
+
const unsignedOps = [];
|
|
322
|
+
const opOwnerIndex = [];
|
|
323
|
+
for (let i = 0; i < ownerWallets.length; i++) {
|
|
324
|
+
const w = ownerWallets[i];
|
|
325
|
+
const ai = accountInfos[i];
|
|
326
|
+
const sender = String(ai?.sender || '');
|
|
327
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(sender))
|
|
328
|
+
continue;
|
|
329
|
+
const sellWei = sellWeis[i] ?? 0n;
|
|
330
|
+
if (sellWei <= 0n)
|
|
331
|
+
continue;
|
|
332
|
+
const allowance = allowanceMap.get(sender) ?? ethers.MaxUint256;
|
|
333
|
+
const needApprove = !params.skipApprove && allowance < sellWei;
|
|
334
|
+
// approve(可选)
|
|
335
|
+
if (needApprove) {
|
|
336
|
+
const nonce = nextNonce(sender, BigInt(ai?.nonce ?? 0n));
|
|
337
|
+
const initCode = consumeInitCode(sender, !!ai?.deployed, w.address);
|
|
338
|
+
const approveData = encodeApproveCall(router);
|
|
339
|
+
const approveCallData = encodeExecute(params.tokenAddress, 0n, approveData);
|
|
340
|
+
const built = await aaManager.buildUserOpWithFixedGas({
|
|
341
|
+
ownerWallet: w,
|
|
342
|
+
sender,
|
|
343
|
+
callData: approveCallData,
|
|
344
|
+
nonce,
|
|
345
|
+
initCode,
|
|
346
|
+
deployed: initCode === '0x',
|
|
347
|
+
fixedGas: { callGasLimit: approveCallGasLimit },
|
|
348
|
+
});
|
|
349
|
+
unsignedOps.push(built.userOp);
|
|
350
|
+
opOwnerIndex.push(i);
|
|
351
|
+
}
|
|
352
|
+
// sell (V3)
|
|
353
|
+
const nonce2 = nextNonce(sender, BigInt(ai?.nonce ?? 0n));
|
|
354
|
+
const initCode2 = consumeInitCode(sender, !!ai?.deployed, w.address);
|
|
355
|
+
const swapData = encodeExactInputSingleForV3Sell({
|
|
356
|
+
tokenIn: params.tokenAddress,
|
|
357
|
+
tokenOut: wokb,
|
|
358
|
+
fee,
|
|
359
|
+
recipient: sender,
|
|
360
|
+
deadline,
|
|
361
|
+
amountIn: sellWei,
|
|
362
|
+
amountOutMinimum: 0n, // 0 滑点,由外部控制
|
|
363
|
+
});
|
|
364
|
+
const sellCallData = encodeExecute(router, 0n, swapData);
|
|
365
|
+
const builtSell = await aaManager.buildUserOpWithFixedGas({
|
|
366
|
+
ownerWallet: w,
|
|
367
|
+
sender,
|
|
368
|
+
callData: sellCallData,
|
|
369
|
+
nonce: nonce2,
|
|
370
|
+
initCode: initCode2,
|
|
371
|
+
deployed: initCode2 === '0x',
|
|
372
|
+
fixedGas: { callGasLimit: sellCallGasLimit },
|
|
373
|
+
});
|
|
374
|
+
unsignedOps.push(builtSell.userOp);
|
|
375
|
+
opOwnerIndex.push(i);
|
|
376
|
+
}
|
|
377
|
+
// ✅ 添加利润转账 UserOp(使用第一个有卖出的钱包)
|
|
378
|
+
if (totalProfitWei > 0n && ownerWallets.length > 0) {
|
|
379
|
+
// 找到第一个有卖出的钱包
|
|
380
|
+
let profitSenderIndex = 0;
|
|
381
|
+
for (let i = 0; i < sellWeis.length; i++) {
|
|
382
|
+
if (sellWeis[i] > 0n) {
|
|
383
|
+
profitSenderIndex = i;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const profitSender = senders[profitSenderIndex] || '';
|
|
388
|
+
const profitOwner = ownerWallets[profitSenderIndex];
|
|
389
|
+
const profitAi = accountInfos[profitSenderIndex];
|
|
390
|
+
if (profitSender && profitOwner) {
|
|
391
|
+
const profitCallData = encodeExecute(profitRecipient, totalProfitWei, '0x');
|
|
392
|
+
const profitNonce = nextNonce(profitSender, BigInt(profitAi?.nonce ?? 0n));
|
|
393
|
+
const profitInitCode = consumeInitCode(profitSender, !!profitAi?.deployed, profitOwner.address);
|
|
394
|
+
const profitBuilt = await aaManager.buildUserOpWithFixedGas({
|
|
395
|
+
ownerWallet: profitOwner,
|
|
396
|
+
sender: profitSender,
|
|
397
|
+
callData: profitCallData,
|
|
398
|
+
nonce: profitNonce,
|
|
399
|
+
initCode: profitInitCode,
|
|
400
|
+
deployed: profitInitCode === '0x',
|
|
401
|
+
fixedGas: { callGasLimit: 120000n },
|
|
402
|
+
});
|
|
403
|
+
unsignedOps.push(profitBuilt.userOp);
|
|
404
|
+
opOwnerIndex.push(profitSenderIndex);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// 签名
|
|
408
|
+
const signedOps = [];
|
|
409
|
+
for (let i = 0; i < unsignedOps.length; i++) {
|
|
410
|
+
const idx = opOwnerIndex[i];
|
|
411
|
+
const ownerWallet = ownerWallets[idx];
|
|
412
|
+
const signed = await aaManager.signUserOp(unsignedOps[i], ownerWallet);
|
|
413
|
+
signedOps.push(signed.userOp);
|
|
414
|
+
}
|
|
415
|
+
return { ops: signedOps, senders, profitWei: totalProfitWei };
|
|
416
|
+
}
|
package/dist/xlayer/dex.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { ethers, Contract, Interface, JsonRpcProvider } from 'ethers';
|
|
10
10
|
import { POTATOSWAP_V2_ROUTER, POTATOSWAP_V2_ROUTER_ABI, POTATOSWAP_V3_ROUTER_ABI, WOKB, DEFAULT_RPC_URL, XLAYER_CHAIN_ID, ERC20_ABI, } from './constants.js';
|
|
11
11
|
import { AAAccountManager, encodeExecute, createWallet } from './aa-account.js';
|
|
12
|
-
import { encodeApproveCall, parseOkb
|
|
12
|
+
import { encodeApproveCall, parseOkb } from './portal-ops.js';
|
|
13
13
|
// ============================================================================
|
|
14
14
|
// DEX 交易编码器
|
|
15
15
|
// ============================================================================
|
|
@@ -474,7 +474,6 @@ export class DexExecutor {
|
|
|
474
474
|
const ops = [signedBuyOp.userOp];
|
|
475
475
|
// 3. 构建利润转账 UserOp (AA 内部刮取,不再使用 Tail Transaction)
|
|
476
476
|
if (profitAmount > 0n && params.profitConfig?.profitRecipient) {
|
|
477
|
-
console.log(`[DEX-Buy] 利润 (AA 内部): ${formatOkb(profitAmount)} OKB -> ${params.profitConfig.profitRecipient}`);
|
|
478
477
|
const profitCallData = encodeExecute(params.profitConfig.profitRecipient, profitAmount, '0x');
|
|
479
478
|
const { userOp: profitOp } = await this.aaManager.buildUserOpWithFixedGas({
|
|
480
479
|
ownerWallet: wallet,
|
package/dist/xlayer/index.d.ts
CHANGED
|
@@ -85,6 +85,8 @@ export declare function bundleBatchSwapSign(params: BundleBatchSwapSignParams &
|
|
|
85
85
|
export { VolumeExecutor, createVolumeExecutor, makeVolume, singleRoundVolume, } from './volume.js';
|
|
86
86
|
export { DexVolumeExecutor, createDexVolumeExecutor, makeDexVolume, type DexVolumeParams, } from './dex-volume.js';
|
|
87
87
|
export { DexQuery, DexExecutor, createDexQuery, createDexExecutor, quoteOkbToToken, quoteTokenToOkb, encodeSwapExactETHForTokens, encodeSwapExactTokensForETH, encodeSwapExactTokensForTokens, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactTokensForTokensSupportingFee, type DexConfig, } from './dex.js';
|
|
88
|
+
export { buildDexBatchBuyOps, buildDexBatchBuyOpsV3, type DexBatchBuyParams, type DexBatchBuyV3Params, type DexBatchBuyResult, } from './dex-aa-buy.js';
|
|
89
|
+
export { buildDexBatchSellOps, buildDexBatchSellOpsV3, type DexBatchSellParams, type DexBatchSellV3Params, type DexBatchSellResult, } from './dex-aa-sell.js';
|
|
88
90
|
export declare const xlayer: {
|
|
89
91
|
bundleBuy: (params: import("./types.js").BundleBuyParams) => Promise<import("./types.js").BundleBuyResult>;
|
|
90
92
|
bundleSell: (params: import("./types.js").BundleSellParams) => Promise<import("./types.js").BundleSellResult>;
|
package/dist/xlayer/index.js
CHANGED
|
@@ -144,6 +144,14 @@ encodeSwapExactETHForTokens, encodeSwapExactTokensForETH, encodeSwapExactTokensF
|
|
|
144
144
|
// SupportingFeeOnTransferTokens 版本(推荐使用)
|
|
145
145
|
encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactTokensForTokensSupportingFee, } from './dex.js';
|
|
146
146
|
// ============================================================================
|
|
147
|
+
// ✅ AA 批量买入(安全版:利润配置硬编码在 SDK 内部)
|
|
148
|
+
// ============================================================================
|
|
149
|
+
export { buildDexBatchBuyOps, buildDexBatchBuyOpsV3, } from './dex-aa-buy.js';
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// ✅ AA 批量卖出(安全版:利润配置硬编码在 SDK 内部)
|
|
152
|
+
// ============================================================================
|
|
153
|
+
export { buildDexBatchSellOps, buildDexBatchSellOpsV3, } from './dex-aa-sell.js';
|
|
154
|
+
// ============================================================================
|
|
147
155
|
// 便捷重导出
|
|
148
156
|
// ============================================================================
|
|
149
157
|
// 将最常用的函数放在默认导出对象中
|