four-flap-meme-sdk 1.5.32 → 1.5.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/xlayer/bundle.d.ts +2 -0
- package/dist/xlayer/bundle.js +272 -22
- package/dist/xlayer/buy-first-volume.d.ts +18 -0
- package/dist/xlayer/buy-first-volume.js +68 -0
- package/dist/xlayer/dex-bundle-swap.js +150 -19
- package/dist/xlayer/dex-bundle.d.ts +42 -0
- package/dist/xlayer/dex-bundle.js +378 -0
- package/dist/xlayer/dex-buy-first.d.ts +24 -0
- package/dist/xlayer/dex-buy-first.js +404 -0
- package/dist/xlayer/dex-volume.d.ts +30 -0
- package/dist/xlayer/dex-volume.js +80 -0
- package/dist/xlayer/index.d.ts +5 -0
- package/dist/xlayer/index.js +14 -0
- package/dist/xlayer/portal-bundle-swap.js +153 -12
- package/dist/xlayer/portal-buy-first.d.ts +30 -0
- package/dist/xlayer/portal-buy-first.js +415 -0
- package/dist/xlayer/types.d.ts +109 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -53,4 +53,4 @@ export { submitDirectToRpc, submitDirectToRpcSequential, // ✅ 新增:顺序
|
|
|
53
53
|
submitDirectToRpcParallel, type DirectSubmitConfig, type DirectSubmitResult, type DirectTxResult } from './contracts/tm-bundle-merkle/submit.js';
|
|
54
54
|
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';
|
|
55
55
|
export * as XLayer from './xlayer/index.js';
|
|
56
|
-
export { bundleBuy as xlayerBundleBuy, bundleSell as xlayerBundleSell, bundleBuySell as xlayerBundleBuySell, bundleSwapSign as xlayerBundleSwapSign, bundleBatchSwapSign as xlayerBundleBatchSwapSign, createBundleExecutor as xlayerCreateBundleExecutor, BundleExecutor as XLayerBundleExecutor, makeVolume as xlayerMakeVolume, singleRoundVolume as xlayerSingleRoundVolume, createVolumeExecutor as xlayerCreateVolumeExecutor, VolumeExecutor as XLayerVolumeExecutor, 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 BundleSwapResult as XLayerBundleSwapResult, type BundleSwapSignResult as XLayerBundleSwapSignResult, type BundleBatchSwapResult as XLayerBundleBatchSwapResult, type BundleBatchSwapSignResult as XLayerBundleBatchSwapSignResult, type VolumeResult as XLayerVolumeResult, 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';
|
|
56
|
+
export { bundleBuy as xlayerBundleBuy, bundleSell as xlayerBundleSell, bundleBuySell as xlayerBundleBuySell, 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, 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 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
|
@@ -105,6 +105,8 @@ export {
|
|
|
105
105
|
bundleBuy as xlayerBundleBuy, bundleSell as xlayerBundleSell, bundleBuySell as xlayerBundleBuySell, bundleSwapSign as xlayerBundleSwapSign, bundleBatchSwapSign as xlayerBundleBatchSwapSign, createBundleExecutor as xlayerCreateBundleExecutor, BundleExecutor as XLayerBundleExecutor,
|
|
106
106
|
// 刷量
|
|
107
107
|
makeVolume as xlayerMakeVolume, singleRoundVolume as xlayerSingleRoundVolume, createVolumeExecutor as xlayerCreateVolumeExecutor, VolumeExecutor as XLayerVolumeExecutor,
|
|
108
|
+
// ✅ Buy-First 刷量(对齐 BSC buy-first)
|
|
109
|
+
makeBuyFirstVolume as xlayerMakeBuyFirstVolume, BuyFirstVolumeExecutor as XLayerBuyFirstVolumeExecutor, AAPortalBuyFirstExecutor as XLayerAAPortalBuyFirstExecutor, AADexBuyFirstExecutor as XLayerAADexBuyFirstExecutor,
|
|
108
110
|
// DEX 交易
|
|
109
111
|
createDexExecutor as xlayerCreateDexExecutor, createDexQuery as xlayerCreateDexQuery, quoteOkbToToken as xlayerQuoteOkbToToken, quoteTokenToOkb as xlayerQuoteTokenToOkb, DexExecutor as XLayerDexExecutor, DexQuery as XLayerDexQuery,
|
|
110
112
|
// AA 账户管理
|
package/dist/xlayer/bundle.d.ts
CHANGED
|
@@ -71,6 +71,8 @@ export declare class BundleExecutor {
|
|
|
71
71
|
* 捆绑买入
|
|
72
72
|
*
|
|
73
73
|
* 多个地址同一笔 handleOps 买入代币
|
|
74
|
+
* ✅ 对齐 BSC:在买入时从买入金额中扣除利润,买入后立即转账利润(直接转账,无需多跳)
|
|
75
|
+
* ✅ 支持 ERC20 代币作为 quoteToken(如 USDC),利润会报价转换为 OKB 后转账
|
|
74
76
|
*/
|
|
75
77
|
bundleBuy(params: BundleBuyParams): Promise<BundleBuyResult>;
|
|
76
78
|
/**
|
package/dist/xlayer/bundle.js
CHANGED
|
@@ -12,7 +12,8 @@ import { FLAP_PORTAL, ENTRYPOINT_ABI, DEFAULT_CALL_GAS_LIMIT_SELL, DEFAULT_WITHD
|
|
|
12
12
|
import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.js';
|
|
13
13
|
import { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, PortalQuery, parseOkb, formatOkb, } from './portal-ops.js';
|
|
14
14
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
15
|
-
import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
15
|
+
import { PROFIT_CONFIG, ZERO_ADDRESS } from '../utils/constants.js';
|
|
16
|
+
import { DexQuery } from './dex.js';
|
|
16
17
|
// ============================================================================
|
|
17
18
|
// AA Nonce(EntryPoint nonce)本地分配器
|
|
18
19
|
// ============================================================================
|
|
@@ -75,7 +76,8 @@ const DEFAULT_CALL_GAS_LIMIT_TRANSFER = 150000n;
|
|
|
75
76
|
const DEFAULT_CALL_GAS_LIMIT_WITHDRAW = 120000n;
|
|
76
77
|
function resolveProfitSettings(config) {
|
|
77
78
|
const extractProfit = config?.extractProfit !== false; // 默认 true
|
|
78
|
-
|
|
79
|
+
// ✅ 对齐 bundle/刷量模式默认费率(与 BSC bundle-buy-first 语义一致)
|
|
80
|
+
const profitBpsRaw = config?.profitBps ?? PROFIT_CONFIG.RATE_BPS_SWAP;
|
|
79
81
|
const profitBps = Math.max(0, Math.min(10000, Number(profitBpsRaw)));
|
|
80
82
|
const profitRecipient = config?.profitRecipient ?? PROFIT_CONFIG.RECIPIENT;
|
|
81
83
|
return { extractProfit, profitBps, profitRecipient };
|
|
@@ -87,6 +89,14 @@ function calculateProfitWei(amountWei, profitBps) {
|
|
|
87
89
|
return 0n;
|
|
88
90
|
return (amountWei * BigInt(profitBps)) / 10000n;
|
|
89
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* ✅ 对齐 BSC:计算利润和剩余金额(从买入金额中扣除利润)
|
|
94
|
+
*/
|
|
95
|
+
function calculateProfit(amountWei, profitBps) {
|
|
96
|
+
const profit = calculateProfitWei(amountWei, profitBps);
|
|
97
|
+
const remaining = amountWei - profit;
|
|
98
|
+
return { profit, remaining };
|
|
99
|
+
}
|
|
90
100
|
/**
|
|
91
101
|
* XLayer 捆绑交易执行器
|
|
92
102
|
*
|
|
@@ -523,9 +533,11 @@ export class BundleExecutor {
|
|
|
523
533
|
* 捆绑买入
|
|
524
534
|
*
|
|
525
535
|
* 多个地址同一笔 handleOps 买入代币
|
|
536
|
+
* ✅ 对齐 BSC:在买入时从买入金额中扣除利润,买入后立即转账利润(直接转账,无需多跳)
|
|
537
|
+
* ✅ 支持 ERC20 代币作为 quoteToken(如 USDC),利润会报价转换为 OKB 后转账
|
|
526
538
|
*/
|
|
527
539
|
async bundleBuy(params) {
|
|
528
|
-
const { tokenAddress, privateKeys, buyAmounts, transferBackToOwner, config } = params;
|
|
540
|
+
const { tokenAddress, privateKeys, buyAmounts, quoteToken, quoteTokenDecimals, transferBackToOwner, config } = params;
|
|
529
541
|
if (privateKeys.length !== buyAmounts.length) {
|
|
530
542
|
throw new Error('私钥数量和购买金额数量必须一致');
|
|
531
543
|
}
|
|
@@ -537,20 +549,84 @@ export class BundleExecutor {
|
|
|
537
549
|
console.log('=== XLayer Bundle Buy ===');
|
|
538
550
|
console.log('token:', tokenAddress);
|
|
539
551
|
console.log('owners:', wallets.length);
|
|
552
|
+
const effConfig = { ...(this.config ?? {}), ...(config ?? {}) };
|
|
553
|
+
const profitSettings = resolveProfitSettings(effConfig);
|
|
554
|
+
// ✅ 确定 inputToken:如果传入了 quoteToken 且非零地址,则使用它;否则使用零地址(原生代币)
|
|
555
|
+
const inputToken = quoteToken && quoteToken !== ZERO_ADDRESS ? quoteToken : ZERO_ADDRESS;
|
|
556
|
+
const useNativeToken = inputToken === ZERO_ADDRESS;
|
|
557
|
+
// ✅ 对齐 BSC:计算利润并从买入金额中扣除
|
|
558
|
+
// 如果使用非原生代币,需要根据精度转换金额(buyAmounts 是按 18 位精度传入的)
|
|
559
|
+
const originalBuyWeis = buyAmounts.map((a) => parseOkb(a));
|
|
560
|
+
let adjustedOriginalBuyWeis = originalBuyWeis;
|
|
561
|
+
if (!useNativeToken && quoteTokenDecimals !== undefined && quoteTokenDecimals !== 18) {
|
|
562
|
+
const decimalsDiff = 18 - quoteTokenDecimals;
|
|
563
|
+
const divisor = BigInt(10 ** decimalsDiff);
|
|
564
|
+
adjustedOriginalBuyWeis = originalBuyWeis.map(amount => amount / divisor);
|
|
565
|
+
}
|
|
566
|
+
const buyWeis = [];
|
|
567
|
+
const profitWeis = [];
|
|
568
|
+
let totalProfitWei = 0n; // 原生代币利润(OKB)或 ERC20 代币利润(需要报价转换)
|
|
569
|
+
for (let i = 0; i < adjustedOriginalBuyWeis.length; i++) {
|
|
570
|
+
const original = adjustedOriginalBuyWeis[i];
|
|
571
|
+
if (profitSettings.extractProfit) {
|
|
572
|
+
const { profit, remaining } = calculateProfit(original, profitSettings.profitBps);
|
|
573
|
+
buyWeis.push(remaining);
|
|
574
|
+
profitWeis.push(profit);
|
|
575
|
+
totalProfitWei += profit;
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
buyWeis.push(original);
|
|
579
|
+
profitWeis.push(0n);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
// ✅ ERC20 购买:获取代币利润等值的原生代币(OKB)报价
|
|
583
|
+
let nativeProfitAmount = totalProfitWei; // 原生代币购买时直接使用
|
|
584
|
+
if (!useNativeToken && profitSettings.extractProfit && totalProfitWei > 0n) {
|
|
585
|
+
// 将代币利润转换为等值原生代币(OKB)
|
|
586
|
+
const dexQuery = new DexQuery(effConfig);
|
|
587
|
+
nativeProfitAmount = await dexQuery.quoteTokenToOkb(totalProfitWei, inputToken);
|
|
588
|
+
if (nativeProfitAmount === 0n) {
|
|
589
|
+
console.warn(`[警告] ERC20 代币利润报价失败,跳过利润提取`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (profitSettings.extractProfit && nativeProfitAmount > 0n) {
|
|
593
|
+
console.log(`[利润提取] 总利润: ${useNativeToken ? formatOkb(nativeProfitAmount) : `${formatOkb(totalProfitWei)} (ERC20) -> ${formatOkb(nativeProfitAmount)} (OKB)`} -> ${profitSettings.profitRecipient}`);
|
|
594
|
+
}
|
|
540
595
|
// 1. 预取账户信息(并行),并批量估算 gas(减少对 bundler 的 N 次请求)
|
|
541
596
|
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
542
|
-
const buyWeis = buyAmounts.map((a) => parseOkb(a));
|
|
543
597
|
const nonceMap = new AANonceMap();
|
|
544
598
|
for (const ai of accountInfos)
|
|
545
599
|
nonceMap.init(ai.sender, ai.nonce);
|
|
546
600
|
// 估算前确保 sender 有足够余额(用于 bundler 模拟;paymaster 场景会自动跳过)
|
|
547
|
-
//
|
|
601
|
+
// 注意:需要包含原始买入金额(因为利润会在买入后单独转走)
|
|
602
|
+
// 如果使用 ERC20 代币,需要确保 sender 有足够的 ERC20 代币余额
|
|
548
603
|
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
549
|
-
|
|
604
|
+
if (useNativeToken) {
|
|
605
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, originalBuyWeis[i] + parseOkb('0.0003'), `owner${i + 1}/buy-prefund-before-estimate`);
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
// ERC20 代币:确保有足够的 OKB 用于 gas,以及足够的 ERC20 代币用于购买
|
|
609
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, parseOkb('0.0003'), `owner${i + 1}/buy-prefund-before-estimate`);
|
|
610
|
+
// TODO: 如果需要,可以在这里检查并转账 ERC20 代币到 sender
|
|
611
|
+
}
|
|
550
612
|
});
|
|
613
|
+
// ✅ 构建买入 callData:如果使用 ERC20 代币,需要修改 encodeBuyCall 以支持 inputToken
|
|
551
614
|
const buyCallDatas = buyWeis.map((buyWei) => {
|
|
552
|
-
|
|
553
|
-
|
|
615
|
+
// 使用 portal 的 swapExactInput,支持 inputToken 参数
|
|
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
|
+
// 如果使用原生代币,value 为买入金额;否则为 0
|
|
629
|
+
return encodeExecute(this.portalAddress, useNativeToken ? buyWei : 0n, swapData);
|
|
554
630
|
});
|
|
555
631
|
const initCodes = accountInfos.map((ai, i) => ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address));
|
|
556
632
|
const { userOps: buyUserOps, prefundWeis } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
@@ -561,15 +637,50 @@ export class BundleExecutor {
|
|
|
561
637
|
initCode: initCodes[i],
|
|
562
638
|
})),
|
|
563
639
|
});
|
|
564
|
-
// 补足 prefund +
|
|
640
|
+
// 补足 prefund + 买入金额(使用扣除利润后的金额)
|
|
565
641
|
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
566
|
-
|
|
642
|
+
if (useNativeToken) {
|
|
643
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, buyWeis[i] + prefundWeis[i] + parseOkb('0.0002'), `owner${i + 1}/buy-fund`);
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
// ERC20 代币:只需要确保有足够的 OKB 用于 gas
|
|
647
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, prefundWeis[i] + parseOkb('0.0002'), `owner${i + 1}/buy-fund`);
|
|
648
|
+
// TODO: 如果需要,可以在这里检查并转账 ERC20 代币到 sender
|
|
649
|
+
}
|
|
567
650
|
});
|
|
568
651
|
// 签名(受控并发,避免大规模时阻塞)
|
|
569
652
|
const signedBuy = await mapWithConcurrency(buyUserOps, 10, async (op, i) => this.aaManager.signUserOp(op, wallets[i]));
|
|
570
653
|
const buyOps = signedBuy.map((s) => s.userOp);
|
|
571
|
-
//
|
|
572
|
-
|
|
654
|
+
// ✅ 利润转账 UserOps(买入后立即转走利润,直接转账无需多跳)
|
|
655
|
+
// ✅ 对齐 BSC:如果使用 ERC20 代币,利润已通过报价转换为 OKB,直接转账 OKB
|
|
656
|
+
const profitOps = [];
|
|
657
|
+
if (profitSettings.extractProfit && nativeProfitAmount > 0n) {
|
|
658
|
+
// 找到利润最大的钱包作为利润转账的 sender(对齐 BSC 的 maxFundsIndex 逻辑)
|
|
659
|
+
const maxProfitIndex = profitWeis.reduce((maxIdx, profit, idx) => profit > profitWeis[maxIdx] ? idx : maxIdx, 0);
|
|
660
|
+
const profitSender = accountInfos[maxProfitIndex].sender;
|
|
661
|
+
const profitOwner = wallets[maxProfitIndex];
|
|
662
|
+
// 利润转账:直接转账 OKB(无论原始是原生代币还是 ERC20 代币,最终都转 OKB)
|
|
663
|
+
const profitCallData = encodeExecute(profitSettings.profitRecipient, nativeProfitAmount, '0x');
|
|
664
|
+
const { userOp: profitOp, prefundWei: profitPrefund } = await this.aaManager.buildUserOpWithFixedGas({
|
|
665
|
+
ownerWallet: profitOwner,
|
|
666
|
+
sender: profitSender,
|
|
667
|
+
nonce: nonceMap.next(profitSender),
|
|
668
|
+
initCode: initCodes[maxProfitIndex] === '0x' ? '0x' : initCodes[maxProfitIndex],
|
|
669
|
+
callData: profitCallData,
|
|
670
|
+
deployed: accountInfos[maxProfitIndex].deployed,
|
|
671
|
+
fixedGas: {
|
|
672
|
+
...(effConfig.fixedGas ?? {}),
|
|
673
|
+
callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
// 确保 profit sender 有足够余额(用于支付利润转账的 gas)
|
|
677
|
+
await this.aaManager.ensureSenderBalance(profitOwner, profitSender, profitPrefund + parseOkb('0.0001'), `profit-transfer-fund`);
|
|
678
|
+
const signedProfit = await this.aaManager.signUserOp(profitOp, profitOwner);
|
|
679
|
+
profitOps.push(signedProfit.userOp);
|
|
680
|
+
}
|
|
681
|
+
// 2. 执行买入 + 利润转账(同一笔 handleOps)
|
|
682
|
+
const allBuyOps = [...buyOps, ...profitOps];
|
|
683
|
+
const buyResult = await this.runHandleOps('buyBundle', allBuyOps, bundlerSigner, beneficiary);
|
|
573
684
|
if (!buyResult) {
|
|
574
685
|
throw new Error('买入交易失败');
|
|
575
686
|
}
|
|
@@ -706,6 +817,9 @@ export class BundleExecutor {
|
|
|
706
817
|
}
|
|
707
818
|
// 3. 可选:归集 OKB
|
|
708
819
|
let withdrawResult;
|
|
820
|
+
let totalProfitWei = 0n;
|
|
821
|
+
const effProfitCfg = { ...(this.config ?? {}), ...(config ?? {}) };
|
|
822
|
+
const profitSettings = resolveProfitSettings(effProfitCfg);
|
|
709
823
|
if (withdrawToOwner) {
|
|
710
824
|
const withdrawOps = [];
|
|
711
825
|
// 批量获取 sender OKB 余额
|
|
@@ -731,6 +845,12 @@ export class BundleExecutor {
|
|
|
731
845
|
ownerName: `owner${it.i + 1}`,
|
|
732
846
|
configOverride: config,
|
|
733
847
|
});
|
|
848
|
+
if (signed && profitSettings.extractProfit) {
|
|
849
|
+
const withdrawAmount = it.senderBalance > signed.prefundWei + reserveWei
|
|
850
|
+
? it.senderBalance - signed.prefundWei - reserveWei
|
|
851
|
+
: 0n;
|
|
852
|
+
totalProfitWei += calculateProfitWei(withdrawAmount, profitSettings.profitBps);
|
|
853
|
+
}
|
|
734
854
|
if (signed?.userOp)
|
|
735
855
|
nonceMap.commit(it.sender, it.nonce);
|
|
736
856
|
return signed?.userOp ?? null;
|
|
@@ -743,7 +863,19 @@ export class BundleExecutor {
|
|
|
743
863
|
withdrawResult = await this.runHandleOps('withdrawBundle', withdrawOps, bundlerSigner, beneficiary) ?? undefined;
|
|
744
864
|
}
|
|
745
865
|
}
|
|
746
|
-
return {
|
|
866
|
+
return {
|
|
867
|
+
approveResult,
|
|
868
|
+
sellResult,
|
|
869
|
+
withdrawResult,
|
|
870
|
+
profit: withdrawToOwner
|
|
871
|
+
? {
|
|
872
|
+
extractProfit: profitSettings.extractProfit,
|
|
873
|
+
profitBps: profitSettings.profitBps,
|
|
874
|
+
profitRecipient: profitSettings.profitRecipient,
|
|
875
|
+
totalProfitWei: totalProfitWei.toString(),
|
|
876
|
+
}
|
|
877
|
+
: undefined,
|
|
878
|
+
};
|
|
747
879
|
}
|
|
748
880
|
/**
|
|
749
881
|
* 捆绑买卖(先买后卖)
|
|
@@ -751,7 +883,7 @@ export class BundleExecutor {
|
|
|
751
883
|
* 完整流程:买入 -> 授权 -> 卖出 -> 归集
|
|
752
884
|
*/
|
|
753
885
|
async bundleBuySell(params) {
|
|
754
|
-
const { tokenAddress, privateKeys, buyAmounts, sellPercent = 100, withdrawToOwner = true, withdrawReserve = DEFAULT_WITHDRAW_RESERVE, config, } = params;
|
|
886
|
+
const { tokenAddress, privateKeys, buyAmounts, quoteToken, quoteTokenDecimals, sellPercent = 100, withdrawToOwner = true, withdrawReserve = DEFAULT_WITHDRAW_RESERVE, config, } = params;
|
|
755
887
|
if (privateKeys.length !== buyAmounts.length) {
|
|
756
888
|
throw new Error('私钥数量和购买金额数量必须一致');
|
|
757
889
|
}
|
|
@@ -770,17 +902,81 @@ export class BundleExecutor {
|
|
|
770
902
|
const nonceMap = new AANonceMap();
|
|
771
903
|
for (const ai of accountInfos)
|
|
772
904
|
nonceMap.init(ai.sender, ai.nonce);
|
|
905
|
+
const effProfitCfgBuySell = { ...(this.config ?? {}), ...(config ?? {}) };
|
|
906
|
+
const profitSettingsBuySell = resolveProfitSettings(effProfitCfgBuySell);
|
|
907
|
+
// ✅ 确定 inputToken:如果传入了 quoteToken 且非零地址,则使用它;否则使用零地址(原生代币)
|
|
908
|
+
const inputToken = quoteToken && quoteToken !== ZERO_ADDRESS ? quoteToken : ZERO_ADDRESS;
|
|
909
|
+
const useNativeToken = inputToken === ZERO_ADDRESS;
|
|
910
|
+
// ✅ 对齐 BSC:计算利润并从买入金额中扣除
|
|
911
|
+
// 如果使用非原生代币,需要根据精度转换金额(buyAmounts 是按 18 位精度传入的)
|
|
912
|
+
const originalBuyWeis = buyAmounts.map((a) => parseOkb(a));
|
|
913
|
+
let adjustedOriginalBuyWeis = originalBuyWeis;
|
|
914
|
+
if (!useNativeToken && quoteTokenDecimals !== undefined && quoteTokenDecimals !== 18) {
|
|
915
|
+
const decimalsDiff = 18 - quoteTokenDecimals;
|
|
916
|
+
const divisor = BigInt(10 ** decimalsDiff);
|
|
917
|
+
adjustedOriginalBuyWeis = originalBuyWeis.map(amount => amount / divisor);
|
|
918
|
+
}
|
|
919
|
+
const buyWeis = [];
|
|
920
|
+
const profitWeis = [];
|
|
921
|
+
let totalBuyProfitWei = 0n; // 原生代币利润(OKB)或 ERC20 代币利润(需要报价转换)
|
|
922
|
+
for (let i = 0; i < adjustedOriginalBuyWeis.length; i++) {
|
|
923
|
+
const original = adjustedOriginalBuyWeis[i];
|
|
924
|
+
if (profitSettingsBuySell.extractProfit) {
|
|
925
|
+
const { profit, remaining } = calculateProfit(original, profitSettingsBuySell.profitBps);
|
|
926
|
+
buyWeis.push(remaining);
|
|
927
|
+
profitWeis.push(profit);
|
|
928
|
+
totalBuyProfitWei += profit;
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
buyWeis.push(original);
|
|
932
|
+
profitWeis.push(0n);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
// ✅ ERC20 购买:获取代币利润等值的原生代币(OKB)报价
|
|
936
|
+
let nativeBuyProfitAmount = totalBuyProfitWei; // 原生代币购买时直接使用
|
|
937
|
+
if (!useNativeToken && profitSettingsBuySell.extractProfit && totalBuyProfitWei > 0n) {
|
|
938
|
+
// 将代币利润转换为等值原生代币(OKB)
|
|
939
|
+
const dexQuery = new DexQuery(effProfitCfgBuySell);
|
|
940
|
+
nativeBuyProfitAmount = await dexQuery.quoteTokenToOkb(totalBuyProfitWei, inputToken);
|
|
941
|
+
if (nativeBuyProfitAmount === 0n) {
|
|
942
|
+
console.warn(`[警告] ERC20 代币利润报价失败,跳过利润提取`);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (profitSettingsBuySell.extractProfit && nativeBuyProfitAmount > 0n) {
|
|
946
|
+
console.log(`[利润提取-买入] 总利润: ${useNativeToken ? formatOkb(nativeBuyProfitAmount) : `${formatOkb(totalBuyProfitWei)} (ERC20) -> ${formatOkb(nativeBuyProfitAmount)} (OKB)`} -> ${profitSettingsBuySell.profitRecipient}`);
|
|
947
|
+
}
|
|
773
948
|
// 1. 买入(批量估算 + 并发补余额 + 并发签名)
|
|
774
|
-
const buyWeis = buyAmounts.map((a) => parseOkb(a));
|
|
775
949
|
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
776
950
|
const buyWei = buyWeis[i] ?? 0n;
|
|
777
951
|
if (buyWei <= 0n)
|
|
778
952
|
return;
|
|
779
|
-
|
|
953
|
+
if (useNativeToken) {
|
|
954
|
+
// 注意:需要包含原始买入金额(因为利润会在买入后单独转走)
|
|
955
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, originalBuyWeis[i] + parseOkb('0.0003'), `owner${i + 1}/buy-prefund-before-estimate`);
|
|
956
|
+
}
|
|
957
|
+
else {
|
|
958
|
+
// ERC20 代币:确保有足够的 OKB 用于 gas
|
|
959
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, parseOkb('0.0003'), `owner${i + 1}/buy-prefund-before-estimate`);
|
|
960
|
+
// TODO: 如果需要,可以在这里检查并转账 ERC20 代币到 sender
|
|
961
|
+
}
|
|
780
962
|
});
|
|
963
|
+
// ✅ 构建买入 callData:如果使用 ERC20 代币,需要修改 encodeBuyCall 以支持 inputToken
|
|
781
964
|
const buyCallDatas = buyWeis.map((buyWei) => {
|
|
782
|
-
|
|
783
|
-
|
|
965
|
+
// 使用 portal 的 swapExactInput,支持 inputToken 参数
|
|
966
|
+
const portalIface = new Interface([
|
|
967
|
+
'function swapExactInput((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData) params) payable returns (uint256)',
|
|
968
|
+
]);
|
|
969
|
+
const swapData = portalIface.encodeFunctionData('swapExactInput', [
|
|
970
|
+
{
|
|
971
|
+
inputToken,
|
|
972
|
+
outputToken: tokenAddress,
|
|
973
|
+
inputAmount: buyWei,
|
|
974
|
+
minOutputAmount: 0n,
|
|
975
|
+
permitData: '0x',
|
|
976
|
+
},
|
|
977
|
+
]);
|
|
978
|
+
// 如果使用原生代币,value 为买入金额;否则为 0
|
|
979
|
+
return encodeExecute(this.portalAddress, useNativeToken ? buyWei : 0n, swapData);
|
|
784
980
|
});
|
|
785
981
|
const initCodes = accountInfos.map((ai, i) => (ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address)));
|
|
786
982
|
const { userOps: buyUserOps, prefundWeis } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
@@ -793,11 +989,44 @@ export class BundleExecutor {
|
|
|
793
989
|
});
|
|
794
990
|
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
795
991
|
const buyWei = buyWeis[i] ?? 0n;
|
|
796
|
-
|
|
992
|
+
if (useNativeToken) {
|
|
993
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, buyWei + (prefundWeis[i] ?? 0n) + parseOkb('0.0002'), `owner${i + 1}/buy-fund`);
|
|
994
|
+
}
|
|
995
|
+
else {
|
|
996
|
+
// ERC20 代币:只需要确保有足够的 OKB 用于 gas
|
|
997
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, (prefundWeis[i] ?? 0n) + parseOkb('0.0002'), `owner${i + 1}/buy-fund`);
|
|
998
|
+
// TODO: 如果需要,可以在这里检查并转账 ERC20 代币到 sender
|
|
999
|
+
}
|
|
797
1000
|
});
|
|
798
1001
|
const signedBuy = await mapWithConcurrency(buyUserOps, 10, async (op, i) => this.aaManager.signUserOp(op, wallets[i]));
|
|
799
1002
|
const buyOps = signedBuy.map((s) => s.userOp);
|
|
800
|
-
|
|
1003
|
+
// ✅ 利润转账 UserOps(买入后立即转走利润,直接转账无需多跳)
|
|
1004
|
+
// ✅ 对齐 BSC:如果使用 ERC20 代币,利润已通过报价转换为 OKB,直接转账 OKB
|
|
1005
|
+
const profitOps = [];
|
|
1006
|
+
if (profitSettingsBuySell.extractProfit && nativeBuyProfitAmount > 0n) {
|
|
1007
|
+
const maxProfitIndex = profitWeis.reduce((maxIdx, profit, idx) => profit > profitWeis[maxIdx] ? idx : maxIdx, 0);
|
|
1008
|
+
const profitSender = accountInfos[maxProfitIndex].sender;
|
|
1009
|
+
const profitOwner = wallets[maxProfitIndex];
|
|
1010
|
+
// 利润转账:直接转账 OKB(无论原始是原生代币还是 ERC20 代币,最终都转 OKB)
|
|
1011
|
+
const profitCallData = encodeExecute(profitSettingsBuySell.profitRecipient, nativeBuyProfitAmount, '0x');
|
|
1012
|
+
const { userOp: profitOp, prefundWei: profitPrefund } = await this.aaManager.buildUserOpWithFixedGas({
|
|
1013
|
+
ownerWallet: profitOwner,
|
|
1014
|
+
sender: profitSender,
|
|
1015
|
+
nonce: nonceMap.next(profitSender),
|
|
1016
|
+
initCode: initCodes[maxProfitIndex] === '0x' ? '0x' : initCodes[maxProfitIndex],
|
|
1017
|
+
callData: profitCallData,
|
|
1018
|
+
deployed: accountInfos[maxProfitIndex].deployed,
|
|
1019
|
+
fixedGas: {
|
|
1020
|
+
...(effProfitCfgBuySell.fixedGas ?? {}),
|
|
1021
|
+
callGasLimit: effProfitCfgBuySell.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
|
|
1022
|
+
},
|
|
1023
|
+
});
|
|
1024
|
+
await this.aaManager.ensureSenderBalance(profitOwner, profitSender, profitPrefund + parseOkb('0.0001'), `profit-transfer-fund`);
|
|
1025
|
+
const signedProfit = await this.aaManager.signUserOp(profitOp, profitOwner);
|
|
1026
|
+
profitOps.push(signedProfit.userOp);
|
|
1027
|
+
}
|
|
1028
|
+
const allBuyOps = [...buyOps, ...profitOps];
|
|
1029
|
+
const buyResult = await this.runHandleOps('buyBundle', allBuyOps, bundlerSigner, beneficiary);
|
|
801
1030
|
if (!buyResult) {
|
|
802
1031
|
throw new Error('买入交易失败');
|
|
803
1032
|
}
|
|
@@ -836,8 +1065,9 @@ export class BundleExecutor {
|
|
|
836
1065
|
if (!sellResult) {
|
|
837
1066
|
throw new Error('卖出交易失败');
|
|
838
1067
|
}
|
|
839
|
-
// 3. 可选:归集 OKB
|
|
1068
|
+
// 3. 可选:归集 OKB(卖出后的利润在 withdraw 阶段刮取)
|
|
840
1069
|
let withdrawResult;
|
|
1070
|
+
let totalSellWithdrawProfitWei = 0n;
|
|
841
1071
|
if (withdrawToOwner) {
|
|
842
1072
|
const withdrawOps = [];
|
|
843
1073
|
// 批量获取 OKB 余额(sell 后状态)
|
|
@@ -861,6 +1091,12 @@ export class BundleExecutor {
|
|
|
861
1091
|
ownerName: `owner${it.i + 1}`,
|
|
862
1092
|
configOverride: config,
|
|
863
1093
|
});
|
|
1094
|
+
if (signed && profitSettingsBuySell.extractProfit) {
|
|
1095
|
+
const withdrawAmount = it.senderBalance > signed.prefundWei + reserveWei
|
|
1096
|
+
? it.senderBalance - signed.prefundWei - reserveWei
|
|
1097
|
+
: 0n;
|
|
1098
|
+
totalSellWithdrawProfitWei += calculateProfitWei(withdrawAmount, profitSettingsBuySell.profitBps);
|
|
1099
|
+
}
|
|
864
1100
|
if (signed?.userOp)
|
|
865
1101
|
nonceMap.commit(it.sender, it.nonce);
|
|
866
1102
|
return signed?.userOp ?? null;
|
|
@@ -875,7 +1111,21 @@ export class BundleExecutor {
|
|
|
875
1111
|
}
|
|
876
1112
|
// 最终余额
|
|
877
1113
|
const finalBalances = await this.portalQuery.getMultipleOkbBalances(senders);
|
|
878
|
-
return {
|
|
1114
|
+
return {
|
|
1115
|
+
buyResult,
|
|
1116
|
+
sellResult,
|
|
1117
|
+
withdrawResult,
|
|
1118
|
+
finalBalances,
|
|
1119
|
+
profit: withdrawToOwner
|
|
1120
|
+
? {
|
|
1121
|
+
extractProfit: profitSettingsBuySell.extractProfit,
|
|
1122
|
+
profitBps: profitSettingsBuySell.profitBps,
|
|
1123
|
+
profitRecipient: profitSettingsBuySell.profitRecipient,
|
|
1124
|
+
// 总利润 = 买入阶段利润(已转走)+ 卖出后归集阶段的利润
|
|
1125
|
+
totalProfitWei: (totalBuyProfitWei + totalSellWithdrawProfitWei).toString(),
|
|
1126
|
+
}
|
|
1127
|
+
: undefined,
|
|
1128
|
+
};
|
|
879
1129
|
}
|
|
880
1130
|
}
|
|
881
1131
|
// ============================================================================
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer AA Buy-First 自动刷量(对齐 BSC buy-first 思路)
|
|
3
|
+
*
|
|
4
|
+
* - tradeType=FLAP:内盘(Flap Portal)
|
|
5
|
+
* - tradeType=V2:外盘(PotatoSwap V2)
|
|
6
|
+
*
|
|
7
|
+
* 代码实现分离:
|
|
8
|
+
* - 内盘执行器:`portal-buy-first.ts`
|
|
9
|
+
* - 外盘执行器:`dex-buy-first.ts`
|
|
10
|
+
*/
|
|
11
|
+
import type { BuyFirstVolumeParams, BuyFirstVolumeResult, XLayerConfig } from './types.js';
|
|
12
|
+
export declare class BuyFirstVolumeExecutor {
|
|
13
|
+
private config;
|
|
14
|
+
constructor(config?: XLayerConfig);
|
|
15
|
+
execute(params: BuyFirstVolumeParams): Promise<BuyFirstVolumeResult>;
|
|
16
|
+
}
|
|
17
|
+
export declare function createBuyFirstVolumeExecutor(config?: XLayerConfig): BuyFirstVolumeExecutor;
|
|
18
|
+
export declare function makeBuyFirstVolume(params: BuyFirstVolumeParams): Promise<BuyFirstVolumeResult>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer AA Buy-First 自动刷量(对齐 BSC buy-first 思路)
|
|
3
|
+
*
|
|
4
|
+
* - tradeType=FLAP:内盘(Flap Portal)
|
|
5
|
+
* - tradeType=V2:外盘(PotatoSwap V2)
|
|
6
|
+
*
|
|
7
|
+
* 代码实现分离:
|
|
8
|
+
* - 内盘执行器:`portal-buy-first.ts`
|
|
9
|
+
* - 外盘执行器:`dex-buy-first.ts`
|
|
10
|
+
*/
|
|
11
|
+
import { parseOkb, formatOkb } from './portal-ops.js';
|
|
12
|
+
import { AAPortalBuyFirstExecutor } from './portal-buy-first.js';
|
|
13
|
+
import { AADexBuyFirstExecutor } from './dex-buy-first.js';
|
|
14
|
+
function sleep(ms) {
|
|
15
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
|
+
}
|
|
17
|
+
export class BuyFirstVolumeExecutor {
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
}
|
|
21
|
+
async execute(params) {
|
|
22
|
+
const { tradeType = 'FLAP', buyerFundsPerRound, rounds, intervalMs = 3000, config, ...rest } = params;
|
|
23
|
+
const effConfig = { ...(this.config ?? {}), ...(config ?? {}) };
|
|
24
|
+
const roundResults = [];
|
|
25
|
+
let successRounds = 0;
|
|
26
|
+
let failedRounds = 0;
|
|
27
|
+
let totalVolume = 0n;
|
|
28
|
+
const perRoundWei = parseOkb(String(buyerFundsPerRound));
|
|
29
|
+
for (let r = 0; r < rounds; r++) {
|
|
30
|
+
console.log(`\n========== AA BuyFirst Round ${r + 1}/${rounds} (${tradeType}) ==========`);
|
|
31
|
+
try {
|
|
32
|
+
const execParams = {
|
|
33
|
+
tradeType,
|
|
34
|
+
buyerFunds: buyerFundsPerRound,
|
|
35
|
+
config: effConfig,
|
|
36
|
+
...rest,
|
|
37
|
+
};
|
|
38
|
+
const result = tradeType === 'FLAP'
|
|
39
|
+
? await new AAPortalBuyFirstExecutor(effConfig).execute(execParams)
|
|
40
|
+
: await new AADexBuyFirstExecutor(effConfig).execute(execParams);
|
|
41
|
+
roundResults.push(result);
|
|
42
|
+
successRounds++;
|
|
43
|
+
// 交易量按“买入资金 * 2(买+卖)”统计(与 BSC buy-first 口径更接近)
|
|
44
|
+
totalVolume += perRoundWei * 2n;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
console.error(`AA BuyFirst Round ${r + 1} 失败:`, e);
|
|
48
|
+
failedRounds++;
|
|
49
|
+
}
|
|
50
|
+
if (r < rounds - 1 && intervalMs > 0) {
|
|
51
|
+
console.log(`等待 ${intervalMs}ms...`);
|
|
52
|
+
await sleep(intervalMs);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
console.log('\n=== AA BuyFirst Volume Summary ===');
|
|
56
|
+
console.log('成功轮数:', successRounds);
|
|
57
|
+
console.log('失败轮数:', failedRounds);
|
|
58
|
+
console.log('总交易量:', formatOkb(totalVolume), 'OKB');
|
|
59
|
+
return { successRounds, failedRounds, roundResults, totalVolume };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function createBuyFirstVolumeExecutor(config) {
|
|
63
|
+
return new BuyFirstVolumeExecutor(config);
|
|
64
|
+
}
|
|
65
|
+
export async function makeBuyFirstVolume(params) {
|
|
66
|
+
const executor = createBuyFirstVolumeExecutor(params.config);
|
|
67
|
+
return executor.execute(params);
|
|
68
|
+
}
|