four-flap-meme-sdk 1.6.53 → 1.6.55

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.
@@ -10,7 +10,7 @@ import { generateWallets } from './wallet.js';
10
10
  import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT, getDeadline } from './bundle-helpers.js';
11
11
  import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA, PROFIT_CONFIG } from './constants.js';
12
12
  import { FLAP_PORTAL_ADDRESSES } from '../flap/constants.js';
13
- import { V2_ROUTER_ABI, V3_ROUTER02_ABI, ERC20_ABI } from '../abis/common.js';
13
+ import { V2_ROUTER_ABI, V3_ROUTER02_ABI, V3_ROUTER_LEGACY_ABI, ERC20_ABI } from '../abis/common.js';
14
14
  // Four 内盘 ABI
15
15
  const FOUR_TM2_ABI = [
16
16
  'function buyTokenAMAP(uint256 origin, address token, address to, uint256 funds, uint256 minAmount) payable'
@@ -423,32 +423,59 @@ async function buildV2BuyTx(wallet, tokenAddress, buyAmount, nonce, gasPrice, ga
423
423
  * 构建 V3 单跳买入交易
424
424
  * BNB/OKB → Token (使用 exactInputSingle + multicall)
425
425
  *
426
- * ✅ 修复:使用 ethers.Interface 手动编码 calldata,避免 multicall 重载问题
426
+ * ✅ 修复:根据链类型选择正确的 Router ABI 版本
427
+ * - XLayer/Monad: 旧版 Router(exactInputSingle 包含 deadline,multicall(bytes[]))
428
+ * - BSC: 新版 SwapRouter02(exactInputSingle 不含 deadline,multicall(uint256,bytes[]))
427
429
  */
428
430
  async function buildV3BuyTx(wallet, tokenAddress, buyAmount, nonce, gasPrice, gasLimit, chainId, txType, v3Fee = 2500, // 默认 0.25% 手续费
429
431
  chain) {
430
432
  const deadline = getDeadline();
431
- const v3RouterIface = new ethers.Interface(V3_ROUTER02_ABI);
432
433
  const wrappedNative = getWrappedNativeAddress(chain);
433
434
  const routerAddress = chain === 'XLAYER' ? XLAYER_V3_ROUTER_ADDRESS : PANCAKE_V3_ROUTER_ADDRESS;
434
- // 构建 exactInputSingle calldata
435
- const swapParams = {
436
- tokenIn: wrappedNative,
437
- tokenOut: tokenAddress,
438
- fee: v3Fee,
439
- recipient: wallet.address,
440
- amountIn: buyAmount,
441
- amountOutMinimum: 0n,
442
- sqrtPriceLimitX96: 0n
443
- };
444
- const exactInputSingleData = v3RouterIface.encodeFunctionData('exactInputSingle', [swapParams]);
445
- // ✅ 添加 refundETH,退还多余的 ETH/OKB
446
- const refundETHData = v3RouterIface.encodeFunctionData('refundETH', []);
447
- // ✅ 使用明确的函数签名编码 multicall,避免重载问题
448
- const multicallData = v3RouterIface.encodeFunctionData('multicall(uint256,bytes[])', [
449
- deadline,
450
- [exactInputSingleData, refundETHData]
451
- ]);
435
+ // 根据链类型选择正确的 ABI 版本
436
+ // XLayer/Monad 使用旧版 Router(exactInputSingle 包含 deadline)
437
+ // BSC 使用新版 SwapRouter02(exactInputSingle 不含 deadline)
438
+ const useLegacyRouter = chain === 'XLAYER' || chain === 'MONAD';
439
+ const v3RouterIface = new ethers.Interface(useLegacyRouter ? V3_ROUTER_LEGACY_ABI : V3_ROUTER02_ABI);
440
+ let multicallData;
441
+ if (useLegacyRouter) {
442
+ // ✅ 旧版 Router:swapParams 包含 deadline,multicall(bytes[])
443
+ const swapParams = {
444
+ tokenIn: wrappedNative,
445
+ tokenOut: tokenAddress,
446
+ fee: v3Fee,
447
+ recipient: wallet.address,
448
+ deadline, // ✅ 旧版包含 deadline
449
+ amountIn: buyAmount,
450
+ amountOutMinimum: 0n,
451
+ sqrtPriceLimitX96: 0n
452
+ };
453
+ const exactInputSingleData = v3RouterIface.encodeFunctionData('exactInputSingle', [swapParams]);
454
+ const refundETHData = v3RouterIface.encodeFunctionData('refundETH', []);
455
+ // ✅ 旧版 multicall:只有 bytes[] 参数
456
+ multicallData = v3RouterIface.encodeFunctionData('multicall(bytes[])', [
457
+ [exactInputSingleData, refundETHData]
458
+ ]);
459
+ }
460
+ else {
461
+ // ✅ 新版 SwapRouter02:swapParams 不含 deadline,multicall(uint256,bytes[])
462
+ const swapParams = {
463
+ tokenIn: wrappedNative,
464
+ tokenOut: tokenAddress,
465
+ fee: v3Fee,
466
+ recipient: wallet.address,
467
+ amountIn: buyAmount,
468
+ amountOutMinimum: 0n,
469
+ sqrtPriceLimitX96: 0n
470
+ };
471
+ const exactInputSingleData = v3RouterIface.encodeFunctionData('exactInputSingle', [swapParams]);
472
+ const refundETHData = v3RouterIface.encodeFunctionData('refundETH', []);
473
+ // ✅ 新版 multicall:deadline 作为第一个参数
474
+ multicallData = v3RouterIface.encodeFunctionData('multicall(uint256,bytes[])', [
475
+ deadline,
476
+ [exactInputSingleData, refundETHData]
477
+ ]);
478
+ }
452
479
  const tx = {
453
480
  to: routerAddress,
454
481
  data: multicallData,
@@ -0,0 +1,102 @@
1
+ /**
2
+ * XLayer AA 捆绑刷量模式(Bundle Buy-First)
3
+ *
4
+ * 核心逻辑:多钱包分开执行买入和卖出,所有操作合并到一个 handleOps 中原子执行
5
+ *
6
+ * 结构:[买入Ops...] + [卖出Ops...] + [统一利润Op]
7
+ *
8
+ * 支持池子类型:
9
+ * - flap: 内盘 (Portal)
10
+ * - v2: 土豆 V2 外盘
11
+ * - v3: 土豆 V3 外盘
12
+ *
13
+ * 安全说明:利润配置硬编码在 SDK 内部,前端无法篡改
14
+ */
15
+ import type { UserOperation, XLayerConfig } from './types.js';
16
+ export type BundlePoolType = 'flap' | 'v2' | 'v3';
17
+ export interface BundleBuyOpsParams {
18
+ /** 代币地址 */
19
+ tokenAddress: string;
20
+ /** 买家 Owner 私钥列表 */
21
+ ownerPrivateKeys: string[];
22
+ /** 每个 Owner 的买入金额 (OKB) */
23
+ buyAmountsOkb: string[];
24
+ /** 池子类型 */
25
+ poolType?: BundlePoolType;
26
+ /** V2/V3 Router 地址(poolType 为 v2/v3 时必填) */
27
+ routerAddress?: string;
28
+ /** V3 pool fee */
29
+ v3Fee?: number;
30
+ /** Wrapped OKB 地址(默认 WOKB) */
31
+ wrappedOkbAddress?: string;
32
+ /** 交易截止时间(分钟),默认 20 */
33
+ deadlineMinutes?: number;
34
+ /** SDK 配置 */
35
+ config?: Partial<XLayerConfig>;
36
+ }
37
+ export interface BundleSellOpsParams {
38
+ /** 代币地址 */
39
+ tokenAddress: string;
40
+ /** 卖家 Owner 私钥列表 */
41
+ ownerPrivateKeys: string[];
42
+ /** 每个 Owner 要卖出的代币数量(字符串) */
43
+ sellAmounts: string[];
44
+ /** 代币精度(默认 18) */
45
+ tokenDecimals?: number;
46
+ /** 池子类型 */
47
+ poolType?: BundlePoolType;
48
+ /** V2/V3 Router 地址 */
49
+ routerAddress?: string;
50
+ /** V3 pool fee */
51
+ v3Fee?: number;
52
+ /** Wrapped OKB 地址 */
53
+ wrappedOkbAddress?: string;
54
+ /** 交易截止时间(分钟) */
55
+ deadlineMinutes?: number;
56
+ /** Nonce 偏移量映射(用于处理同一 sender 既买又卖的情况) */
57
+ nonceOffsetMap?: Map<string, number>;
58
+ /** 是否跳过授权(假设已预先授权) */
59
+ skipApprove?: boolean;
60
+ /** SDK 配置 */
61
+ config?: Partial<XLayerConfig>;
62
+ }
63
+ export interface BundleOpsResult {
64
+ /** 构建的 UserOps */
65
+ ops: UserOperation[];
66
+ /** Sender 地址列表 */
67
+ senders: string[];
68
+ /** 利润金额 (wei) */
69
+ profitWei: bigint;
70
+ }
71
+ export interface BundleProfitOpParams {
72
+ /** Owner 私钥 */
73
+ ownerPrivateKey: string;
74
+ /** 利润金额 (wei) */
75
+ profitWei: bigint;
76
+ /** Nonce 偏移量 */
77
+ nonceOffset?: number;
78
+ /** SDK 配置 */
79
+ config?: Partial<XLayerConfig>;
80
+ }
81
+ /**
82
+ * 构建捆绑买入 UserOps
83
+ */
84
+ export declare function buildBundleBuyOps(params: BundleBuyOpsParams): Promise<BundleOpsResult>;
85
+ /**
86
+ * 构建捆绑卖出 UserOps
87
+ */
88
+ export declare function buildBundleSellOps(params: BundleSellOpsParams): Promise<BundleOpsResult>;
89
+ /**
90
+ * 构建独立的利润 UserOp
91
+ */
92
+ export declare function buildBundleProfitOp(params: BundleProfitOpParams): Promise<{
93
+ op: UserOperation;
94
+ sender: string;
95
+ } | null>;
96
+ /**
97
+ * 过滤掉利润 UserOps(通过检查 callData 是否包含利润接收地址来识别)
98
+ */
99
+ export declare function filterOutProfitOps(ops: UserOperation[]): {
100
+ filteredOps: UserOperation[];
101
+ removedCount: number;
102
+ };
@@ -0,0 +1,336 @@
1
+ /**
2
+ * XLayer AA 捆绑刷量模式(Bundle Buy-First)
3
+ *
4
+ * 核心逻辑:多钱包分开执行买入和卖出,所有操作合并到一个 handleOps 中原子执行
5
+ *
6
+ * 结构:[买入Ops...] + [卖出Ops...] + [统一利润Op]
7
+ *
8
+ * 支持池子类型:
9
+ * - flap: 内盘 (Portal)
10
+ * - v2: 土豆 V2 外盘
11
+ * - v3: 土豆 V3 外盘
12
+ *
13
+ * 安全说明:利润配置硬编码在 SDK 内部,前端无法篡改
14
+ */
15
+ import { ethers } from 'ethers';
16
+ import { createAAAccountManager, encodeExecute, createWallet } from './aa-account.js';
17
+ import { encodeBuyCall, encodeSellCall } from './portal-ops.js';
18
+ import { encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHV3, } from './dex.js';
19
+ import { encodeApproveCall } from './portal-ops.js';
20
+ import { FLAP_PORTAL, WOKB, } from './constants.js';
21
+ import { PROFIT_CONFIG } from '../utils/constants.js';
22
+ import { mapWithConcurrency } from '../utils/concurrency.js';
23
+ // ============================================================================
24
+ // 买入 UserOps 构建
25
+ // ============================================================================
26
+ /**
27
+ * 构建捆绑买入 UserOps
28
+ */
29
+ export async function buildBundleBuyOps(params) {
30
+ const poolType = params.poolType || 'flap';
31
+ const wokb = params.wrappedOkbAddress || WOKB;
32
+ const v3Fee = params.v3Fee || 2500;
33
+ const deadline = Math.floor(Date.now() / 1000) + 60 * Math.max(1, Number(params.deadlineMinutes ?? 20));
34
+ const portal = FLAP_PORTAL;
35
+ const routerAddress = params.routerAddress || '';
36
+ // 参数校验
37
+ if ((poolType === 'v2' || poolType === 'v3') && !routerAddress) {
38
+ throw new Error(`[buildBundleBuyOps] poolType=${poolType} 时必须提供 routerAddress`);
39
+ }
40
+ if (params.ownerPrivateKeys.length !== params.buyAmountsOkb.length) {
41
+ throw new Error('[buildBundleBuyOps] 私钥数量与买入金额数量不一致');
42
+ }
43
+ const config = { ...(params.config ?? {}) };
44
+ const aaManager = createAAAccountManager(config);
45
+ const ownerWallets = params.ownerPrivateKeys.map(pk => createWallet(pk, config));
46
+ const owners = ownerWallets.map(w => w.address);
47
+ const accounts = await aaManager.getMultipleAccountInfo(owners);
48
+ const senders = accounts.map(ai => String(ai?.sender || ''));
49
+ // 利润计算
50
+ const profitBps = PROFIT_CONFIG.RATE_BPS;
51
+ const profitRecipient = PROFIT_CONFIG.RECIPIENT;
52
+ const buyWeis = [];
53
+ let totalProfitWei = 0n;
54
+ for (const amt of params.buyAmountsOkb) {
55
+ const original = ethers.parseEther(String(amt || '0'));
56
+ if (original > 0n) {
57
+ const profit = (original * BigInt(profitBps)) / 10000n;
58
+ buyWeis.push(original - profit);
59
+ totalProfitWei += profit;
60
+ }
61
+ else {
62
+ buyWeis.push(original);
63
+ }
64
+ }
65
+ // Nonce 和 initCode 管理
66
+ const nonceNext = new Map();
67
+ const nextNonce = (sender, startNonce) => {
68
+ const k = sender.toLowerCase();
69
+ if (!nonceNext.has(k))
70
+ nonceNext.set(k, startNonce);
71
+ const n = nonceNext.get(k);
72
+ nonceNext.set(k, n + 1n);
73
+ return n;
74
+ };
75
+ const initCodeBySender = new Map();
76
+ const consumeInitCode = (sender, deployed, ownerAddress) => {
77
+ const k = sender.toLowerCase();
78
+ if (!initCodeBySender.has(k)) {
79
+ initCodeBySender.set(k, deployed ? '0x' : aaManager.generateInitCode(ownerAddress));
80
+ }
81
+ const cur = initCodeBySender.get(k);
82
+ if (cur !== '0x')
83
+ initCodeBySender.set(k, '0x');
84
+ return cur;
85
+ };
86
+ const callGasLimit = 800000n;
87
+ const unsignedOps = [];
88
+ const opOwnerIndex = [];
89
+ for (let i = 0; i < ownerWallets.length; i++) {
90
+ const ownerWallet = ownerWallets[i];
91
+ const acc = accounts[i];
92
+ const sender = String(acc?.sender || '');
93
+ if (!/^0x[a-fA-F0-9]{40}$/.test(sender))
94
+ continue;
95
+ const buyWei = buyWeis[i] ?? 0n;
96
+ if (buyWei <= 0n)
97
+ continue;
98
+ let swapData;
99
+ let targetContract;
100
+ if (poolType === 'flap') {
101
+ swapData = encodeBuyCall(params.tokenAddress, buyWei, 0n);
102
+ targetContract = portal;
103
+ }
104
+ else if (poolType === 'v3') {
105
+ swapData = encodeSwapExactETHForTokensV3({
106
+ tokenIn: wokb,
107
+ tokenOut: params.tokenAddress,
108
+ fee: v3Fee,
109
+ recipient: sender,
110
+ deadline,
111
+ amountIn: buyWei,
112
+ amountOutMinimum: 0n,
113
+ sqrtPriceLimitX96: 0n,
114
+ });
115
+ targetContract = routerAddress;
116
+ }
117
+ else {
118
+ swapData = encodeSwapExactETHForTokensSupportingFee(0n, [wokb, params.tokenAddress], sender, deadline);
119
+ targetContract = routerAddress;
120
+ }
121
+ const callData = encodeExecute(targetContract, buyWei, swapData);
122
+ const nonce = nextNonce(sender, BigInt(acc?.nonce ?? 0n));
123
+ const initCode = consumeInitCode(sender, !!acc?.deployed, ownerWallet.address);
124
+ const { userOp } = await aaManager.buildUserOpWithFixedGas({
125
+ ownerWallet,
126
+ sender,
127
+ callData,
128
+ nonce,
129
+ initCode,
130
+ deployed: initCode === '0x',
131
+ fixedGas: { callGasLimit },
132
+ });
133
+ unsignedOps.push(userOp);
134
+ opOwnerIndex.push(i);
135
+ }
136
+ // 添加利润 UserOp
137
+ if (totalProfitWei > 0n && ownerWallets.length > 0) {
138
+ const profitSenderIndex = 0;
139
+ const profitSender = senders[profitSenderIndex] || '';
140
+ const profitOwner = ownerWallets[profitSenderIndex];
141
+ const profitAi = accounts[profitSenderIndex];
142
+ if (profitSender && profitOwner) {
143
+ const profitCallData = encodeExecute(profitRecipient, totalProfitWei, '0x');
144
+ const profitNonce = nextNonce(profitSender, BigInt(profitAi?.nonce ?? 0n));
145
+ const profitInitCode = consumeInitCode(profitSender, !!profitAi?.deployed, profitOwner.address);
146
+ const { userOp: profitOp } = await aaManager.buildUserOpWithFixedGas({
147
+ ownerWallet: profitOwner,
148
+ sender: profitSender,
149
+ callData: profitCallData,
150
+ nonce: profitNonce,
151
+ initCode: profitInitCode,
152
+ deployed: profitInitCode === '0x',
153
+ fixedGas: { callGasLimit: 120000n },
154
+ });
155
+ unsignedOps.push(profitOp);
156
+ opOwnerIndex.push(profitSenderIndex);
157
+ }
158
+ }
159
+ // 签名
160
+ const signedOps = await mapWithConcurrency(unsignedOps.map((op, i) => ({ op, ownerIdx: opOwnerIndex[i] })), 20, async ({ op, ownerIdx }) => {
161
+ const ownerWallet = ownerWallets[ownerIdx];
162
+ const signed = await aaManager.signUserOp(op, ownerWallet);
163
+ return signed.userOp;
164
+ });
165
+ return { ops: signedOps, senders, profitWei: totalProfitWei };
166
+ }
167
+ // ============================================================================
168
+ // 卖出 UserOps 构建
169
+ // ============================================================================
170
+ /**
171
+ * 构建捆绑卖出 UserOps
172
+ */
173
+ export async function buildBundleSellOps(params) {
174
+ const poolType = params.poolType || 'flap';
175
+ const wokb = params.wrappedOkbAddress || WOKB;
176
+ const v3Fee = params.v3Fee || 2500;
177
+ const deadline = Math.floor(Date.now() / 1000) + 60 * Math.max(1, Number(params.deadlineMinutes ?? 20));
178
+ const portal = FLAP_PORTAL;
179
+ const routerAddress = params.routerAddress || '';
180
+ const tokenDecimals = params.tokenDecimals ?? 18;
181
+ const nonceOffsetMap = params.nonceOffsetMap ?? new Map();
182
+ if ((poolType === 'v2' || poolType === 'v3') && !routerAddress) {
183
+ throw new Error(`[buildBundleSellOps] poolType=${poolType} 时必须提供 routerAddress`);
184
+ }
185
+ if (params.ownerPrivateKeys.length !== params.sellAmounts.length) {
186
+ throw new Error('[buildBundleSellOps] 私钥数量与卖出金额数量不一致');
187
+ }
188
+ const config = { ...(params.config ?? {}) };
189
+ const aaManager = createAAAccountManager(config);
190
+ const ownerWallets = params.ownerPrivateKeys.map(pk => createWallet(pk, config));
191
+ const owners = ownerWallets.map(w => w.address);
192
+ const accounts = await aaManager.getMultipleAccountInfo(owners);
193
+ const senders = accounts.map(ai => String(ai?.sender || ''));
194
+ // 利润计算(从卖出金额中扣除)
195
+ const profitBps = PROFIT_CONFIG.RATE_BPS;
196
+ const profitRecipient = PROFIT_CONFIG.RECIPIENT;
197
+ let totalProfitWei = 0n;
198
+ const unsignedOps = [];
199
+ const opOwnerIndex = [];
200
+ for (let i = 0; i < ownerWallets.length; i++) {
201
+ const ownerWallet = ownerWallets[i];
202
+ const acc = accounts[i];
203
+ const sender = String(acc?.sender || '');
204
+ if (!/^0x[a-fA-F0-9]{40}$/.test(sender))
205
+ continue;
206
+ const sellAmountStr = String(params.sellAmounts[i] || '0');
207
+ const sellAmount = ethers.parseUnits(sellAmountStr, tokenDecimals);
208
+ if (sellAmount <= 0n)
209
+ continue;
210
+ const senderKey = sender.toLowerCase();
211
+ const nonceOffset = nonceOffsetMap.get(senderKey) || 0;
212
+ let currentNonce = BigInt(acc?.nonce ?? 0n) + BigInt(nonceOffset);
213
+ // Approve(如果需要,外盘模式需要先授权)
214
+ if (!params.skipApprove && poolType !== 'flap') {
215
+ const approveCallData = encodeApproveCall(routerAddress);
216
+ const approveExecuteData = encodeExecute(params.tokenAddress, 0n, approveCallData);
217
+ const initCode = acc.deployed ? '0x' : aaManager.generateInitCode(ownerWallet.address);
218
+ const { userOp: approveOp } = await aaManager.buildUserOpWithFixedGas({
219
+ ownerWallet,
220
+ sender,
221
+ callData: approveExecuteData,
222
+ nonce: currentNonce,
223
+ initCode,
224
+ deployed: initCode === '0x',
225
+ fixedGas: { callGasLimit: 100000n },
226
+ });
227
+ unsignedOps.push(approveOp);
228
+ opOwnerIndex.push(i);
229
+ currentNonce += 1n;
230
+ }
231
+ // Sell
232
+ let swapData;
233
+ let targetContract;
234
+ if (poolType === 'flap') {
235
+ swapData = encodeSellCall(params.tokenAddress, sellAmount, 0n);
236
+ targetContract = portal;
237
+ }
238
+ else if (poolType === 'v3') {
239
+ swapData = encodeSwapExactTokensForETHV3({
240
+ tokenIn: params.tokenAddress,
241
+ tokenOut: wokb,
242
+ fee: v3Fee,
243
+ recipient: sender,
244
+ unwrapRecipient: sender,
245
+ deadline,
246
+ amountIn: sellAmount,
247
+ amountOutMinimum: 0n,
248
+ sqrtPriceLimitX96: 0n,
249
+ });
250
+ targetContract = routerAddress;
251
+ }
252
+ else {
253
+ swapData = encodeSwapExactTokensForETHSupportingFee(sellAmount, 0n, [params.tokenAddress, wokb], sender, deadline);
254
+ targetContract = routerAddress;
255
+ }
256
+ const callData = encodeExecute(targetContract, 0n, swapData);
257
+ const initCode = acc.deployed ? '0x' : aaManager.generateInitCode(ownerWallet.address);
258
+ const { userOp } = await aaManager.buildUserOpWithFixedGas({
259
+ ownerWallet,
260
+ sender,
261
+ callData,
262
+ nonce: currentNonce,
263
+ initCode: (currentNonce > BigInt(acc?.nonce ?? 0n)) ? '0x' : initCode, // 如果有 approve,则 initCode 已用
264
+ deployed: true,
265
+ fixedGas: { callGasLimit: 400000n },
266
+ });
267
+ unsignedOps.push(userOp);
268
+ opOwnerIndex.push(i);
269
+ }
270
+ // 签名
271
+ const signedOps = await mapWithConcurrency(unsignedOps.map((op, i) => ({ op, ownerIdx: opOwnerIndex[i] })), 20, async ({ op, ownerIdx }) => {
272
+ const ownerWallet = ownerWallets[ownerIdx];
273
+ const signed = await aaManager.signUserOp(op, ownerWallet);
274
+ return signed.userOp;
275
+ });
276
+ return { ops: signedOps, senders, profitWei: totalProfitWei };
277
+ }
278
+ // ============================================================================
279
+ // 利润 UserOp 构建
280
+ // ============================================================================
281
+ /**
282
+ * 构建独立的利润 UserOp
283
+ */
284
+ export async function buildBundleProfitOp(params) {
285
+ if (params.profitWei <= 0n)
286
+ return null;
287
+ const config = { ...(params.config ?? {}) };
288
+ const aaManager = createAAAccountManager(config);
289
+ const ownerWallet = createWallet(params.ownerPrivateKey, config);
290
+ const accountInfo = await aaManager.getAccountInfo(ownerWallet.address);
291
+ const sender = String(accountInfo?.sender || '');
292
+ if (!/^0x[a-fA-F0-9]{40}$/.test(sender))
293
+ return null;
294
+ const profitRecipient = PROFIT_CONFIG.RECIPIENT;
295
+ const profitCallData = encodeExecute(profitRecipient, params.profitWei, '0x');
296
+ const nonce = BigInt(accountInfo?.nonce ?? 0n) + BigInt(params.nonceOffset || 0);
297
+ const initCode = accountInfo?.deployed ? '0x' : aaManager.generateInitCode(ownerWallet.address);
298
+ const { userOp } = await aaManager.buildUserOpWithFixedGas({
299
+ ownerWallet,
300
+ sender,
301
+ callData: profitCallData,
302
+ nonce,
303
+ initCode,
304
+ deployed: initCode === '0x',
305
+ fixedGas: { callGasLimit: 120000n },
306
+ });
307
+ const signedResult = await aaManager.signUserOp(userOp, ownerWallet);
308
+ const signedOp = signedResult.userOp;
309
+ console.log(`[buildBundleProfitOp] 构建利润 UserOp: sender=${sender.slice(0, 10)}..., profit=${params.profitWei} wei`);
310
+ return { op: signedOp, sender };
311
+ }
312
+ // ============================================================================
313
+ // 过滤利润 UserOps 工具函数
314
+ // ============================================================================
315
+ /**
316
+ * 过滤掉利润 UserOps(通过检查 callData 是否包含利润接收地址来识别)
317
+ */
318
+ export function filterOutProfitOps(ops) {
319
+ const filteredOps = [];
320
+ let removedCount = 0;
321
+ const profitRecipient = PROFIT_CONFIG.RECIPIENT.toLowerCase();
322
+ for (const op of ops) {
323
+ const callData = String(op.callData || '').toLowerCase();
324
+ // 利润 UserOp 的 callData 包含利润接收地址
325
+ if (callData.includes(profitRecipient.slice(2))) {
326
+ // 确认这是一个转账到利润地址的 execute 调用
327
+ const isExecute = callData.startsWith('0xb61d27f6');
328
+ if (isExecute) {
329
+ removedCount++;
330
+ continue;
331
+ }
332
+ }
333
+ filteredOps.push(op);
334
+ }
335
+ return { filteredOps, removedCount };
336
+ }
@@ -3,12 +3,17 @@
3
3
  *
4
4
  * - tradeType=FLAP:内盘(Flap Portal)
5
5
  * - tradeType=V2:外盘(PotatoSwap V2)
6
+ * - tradeType=V3:外盘(PotatoSwap V3)
6
7
  *
7
8
  * 代码实现分离:
8
9
  * - 内盘执行器:`portal-buy-first.ts`
9
10
  * - 外盘执行器:`dex-buy-first.ts`
11
+ * - 无币刷量:`wash-ops.ts`(仅生成签名,不执行)
12
+ * - 捆绑刷量:`bundle-buy-first.ts`(仅生成签名,不执行)
10
13
  */
11
14
  import type { BuyFirstVolumeParams, BuyFirstVolumeResult, XLayerConfig } from './types.js';
15
+ export { buildWashOps, type WashOpsParams, type WashOpsResult, type WashPoolType, } from './wash-ops.js';
16
+ export { buildBundleBuyOps, buildBundleSellOps, buildBundleProfitOp, filterOutProfitOps, type BundleBuyOpsParams, type BundleSellOpsParams, type BundleProfitOpParams, type BundleOpsResult, type BundlePoolType, } from './bundle-buy-first.js';
12
17
  export declare class BuyFirstVolumeExecutor {
13
18
  private config;
14
19
  constructor(config?: XLayerConfig);
@@ -3,14 +3,22 @@
3
3
  *
4
4
  * - tradeType=FLAP:内盘(Flap Portal)
5
5
  * - tradeType=V2:外盘(PotatoSwap V2)
6
+ * - tradeType=V3:外盘(PotatoSwap V3)
6
7
  *
7
8
  * 代码实现分离:
8
9
  * - 内盘执行器:`portal-buy-first.ts`
9
10
  * - 外盘执行器:`dex-buy-first.ts`
11
+ * - 无币刷量:`wash-ops.ts`(仅生成签名,不执行)
12
+ * - 捆绑刷量:`bundle-buy-first.ts`(仅生成签名,不执行)
10
13
  */
11
14
  import { parseOkb } from './portal-ops.js';
12
15
  import { AAPortalBuyFirstExecutor } from './portal-buy-first.js';
13
16
  import { AADexBuyFirstExecutor } from './dex-buy-first.js';
17
+ // ============================================================================
18
+ // ✅ 重新导出无币刷量和捆绑刷量(仅生成签名)
19
+ // ============================================================================
20
+ export { buildWashOps, } from './wash-ops.js';
21
+ export { buildBundleBuyOps, buildBundleSellOps, buildBundleProfitOp, filterOutProfitOps, } from './bundle-buy-first.js';
14
22
  function sleep(ms) {
15
23
  return new Promise((resolve) => setTimeout(resolve, ms));
16
24
  }
@@ -89,6 +89,8 @@ export { DexQuery, DexExecutor, createDexQuery, createDexExecutor, quoteOkbToTok
89
89
  export { buildDexBatchBuyOps, buildDexBatchBuyOpsV3, type DexBatchBuyParams, type DexBatchBuyV3Params, type DexBatchBuyResult, } from './dex-aa-buy.js';
90
90
  export { buildDexBatchSellOps, buildDexBatchSellOpsV3, type DexBatchSellParams, type DexBatchSellV3Params, type DexBatchSellResult, } from './dex-aa-sell.js';
91
91
  export { buildDisperseFromSingleOwnerOpsWithProfit, buildTransfersWithProfit, } from './aa-transfer-profit.js';
92
+ export { buildWashOps, type WashOpsParams, type WashOpsResult, type WashPoolType, } from './wash-ops.js';
93
+ export { buildBundleBuyOps, buildBundleSellOps, buildBundleProfitOp, filterOutProfitOps, type BundleBuyOpsParams, type BundleSellOpsParams, type BundleProfitOpParams, type BundleOpsResult, type BundlePoolType, } from './bundle-buy-first.js';
92
94
  export declare const xlayer: {
93
95
  bundleBuy: (params: import("./types.js").BundleBuyParams) => Promise<import("./types.js").BundleBuyResult>;
94
96
  bundleSell: (params: import("./types.js").BundleSellParams) => Promise<import("./types.js").BundleSellResult>;
@@ -160,6 +160,14 @@ export { buildDexBatchSellOps, buildDexBatchSellOpsV3, } from './dex-aa-sell.js'
160
160
  // ============================================================================
161
161
  export { buildDisperseFromSingleOwnerOpsWithProfit, buildTransfersWithProfit, } from './aa-transfer-profit.js';
162
162
  // ============================================================================
163
+ // ✅ AA 无币刷量模式(Wash Trading)
164
+ // ============================================================================
165
+ export { buildWashOps, } from './wash-ops.js';
166
+ // ============================================================================
167
+ // ✅ AA 捆绑刷量模式(Bundle Buy-First)
168
+ // ============================================================================
169
+ export { buildBundleBuyOps, buildBundleSellOps, buildBundleProfitOp, filterOutProfitOps, } from './bundle-buy-first.js';
170
+ // ============================================================================
163
171
  // 便捷重导出
164
172
  // ============================================================================
165
173
  // 将最常用的函数放在默认导出对象中
@@ -0,0 +1,55 @@
1
+ /**
2
+ * XLayer AA 无币刷量模式(Wash Trading)
3
+ *
4
+ * 核心逻辑:每个钱包在一个 handleOps 中原子执行 [利润刮取 + 买入 + 卖出]
5
+ *
6
+ * 支持池子类型:
7
+ * - flap: 内盘 (Portal)
8
+ * - v2: 土豆 V2 外盘
9
+ * - v3: 土豆 V3 外盘
10
+ *
11
+ * 安全说明:利润配置硬编码在 SDK 内部,前端无法篡改
12
+ * - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
13
+ * - 利润接收者:PROFIT_CONFIG.RECIPIENT
14
+ */
15
+ import type { UserOperation, XLayerConfig } from './types.js';
16
+ export type WashPoolType = 'flap' | 'v2' | 'v3';
17
+ export interface WashOpsParams {
18
+ /** 代币地址 */
19
+ tokenAddress: string;
20
+ /** Owner 私钥列表 */
21
+ ownerPrivateKeys: string[];
22
+ /** 每个 Owner 的买入金额 (OKB) */
23
+ buyAmountsOkb: string[];
24
+ /** 池子类型 */
25
+ poolType?: WashPoolType;
26
+ /** V2/V3 Router 地址(poolType 为 v2/v3 时必填) */
27
+ routerAddress?: string;
28
+ /** V3 pool fee (poolType 为 v3 时必填,e.g., 100, 500, 2500, 10000) */
29
+ v3Fee?: number;
30
+ /** Wrapped OKB 地址(默认 WOKB) */
31
+ wrappedOkbAddress?: string;
32
+ /** 交易截止时间(分钟),默认 20 */
33
+ deadlineMinutes?: number;
34
+ /** SDK 配置 */
35
+ config?: Partial<XLayerConfig>;
36
+ }
37
+ export interface WashOpsResult {
38
+ /** 构建的 UserOps(可直接传递给 handleOps) */
39
+ ops: UserOperation[];
40
+ /** 所有 Sender 地址(唯一) */
41
+ senders: string[];
42
+ /** 总利润金额 (wei) */
43
+ profitWei: bigint;
44
+ }
45
+ /**
46
+ * 构建无币刷量 UserOps
47
+ *
48
+ * 每个钱包生成 2-3 个 UserOps:
49
+ * - [可选] 利润刮取 (nonce=N)
50
+ * - 买入 (nonce=N+1)
51
+ * - 卖出 (nonce=N+2)
52
+ *
53
+ * 所有 UserOps 合并到一个 handleOps 中原子执行
54
+ */
55
+ export declare function buildWashOps(params: WashOpsParams): Promise<WashOpsResult>;
@@ -0,0 +1,248 @@
1
+ /**
2
+ * XLayer AA 无币刷量模式(Wash Trading)
3
+ *
4
+ * 核心逻辑:每个钱包在一个 handleOps 中原子执行 [利润刮取 + 买入 + 卖出]
5
+ *
6
+ * 支持池子类型:
7
+ * - flap: 内盘 (Portal)
8
+ * - v2: 土豆 V2 外盘
9
+ * - v3: 土豆 V3 外盘
10
+ *
11
+ * 安全说明:利润配置硬编码在 SDK 内部,前端无法篡改
12
+ * - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
13
+ * - 利润接收者:PROFIT_CONFIG.RECIPIENT
14
+ */
15
+ import { ethers } from 'ethers';
16
+ import { createAAAccountManager, encodeExecute, createWallet } from './aa-account.js';
17
+ import { encodeBuyCall, encodeSellCall, PortalQuery } from './portal-ops.js';
18
+ import { encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHV3, DexQuery, } from './dex.js';
19
+ import { FLAP_PORTAL, WOKB, } from './constants.js';
20
+ import { PROFIT_CONFIG } from '../utils/constants.js';
21
+ import { mapWithConcurrency } from '../utils/concurrency.js';
22
+ // ============================================================================
23
+ // 核心实现
24
+ // ============================================================================
25
+ /**
26
+ * 构建无币刷量 UserOps
27
+ *
28
+ * 每个钱包生成 2-3 个 UserOps:
29
+ * - [可选] 利润刮取 (nonce=N)
30
+ * - 买入 (nonce=N+1)
31
+ * - 卖出 (nonce=N+2)
32
+ *
33
+ * 所有 UserOps 合并到一个 handleOps 中原子执行
34
+ */
35
+ export async function buildWashOps(params) {
36
+ const poolType = params.poolType || 'flap';
37
+ const wokb = params.wrappedOkbAddress || WOKB;
38
+ const v3Fee = params.v3Fee || 2500;
39
+ const deadline = Math.floor(Date.now() / 1000) + 60 * Math.max(1, Number(params.deadlineMinutes ?? 20));
40
+ const portal = FLAP_PORTAL;
41
+ const routerAddress = params.routerAddress || '';
42
+ // 参数校验
43
+ if (poolType === 'v2' && !routerAddress) {
44
+ throw new Error('[buildWashOps] poolType=v2 时必须提供 routerAddress');
45
+ }
46
+ if (poolType === 'v3' && !routerAddress) {
47
+ throw new Error('[buildWashOps] poolType=v3 时必须提供 routerAddress');
48
+ }
49
+ if (poolType === 'v3' && !params.v3Fee) {
50
+ throw new Error('[buildWashOps] poolType=v3 时必须提供 v3Fee');
51
+ }
52
+ if (params.ownerPrivateKeys.length !== params.buyAmountsOkb.length) {
53
+ throw new Error('[buildWashOps] 私钥数量与买入金额数量不一致');
54
+ }
55
+ const config = { ...(params.config ?? {}) };
56
+ const aaManager = createAAAccountManager(config);
57
+ // 创建 Owner 钱包
58
+ const ownerWallets = params.ownerPrivateKeys.map(pk => createWallet(pk, config));
59
+ const owners = ownerWallets.map(w => w.address);
60
+ // 批量获取 accountInfo
61
+ const accounts = await aaManager.getMultipleAccountInfo(owners);
62
+ // 利润配置(硬编码)
63
+ const profitBps = PROFIT_CONFIG.RATE_BPS;
64
+ const profitRecipient = PROFIT_CONFIG.RECIPIENT;
65
+ // 计算每个钱包的净买入金额(扣除利润后)
66
+ const buyWeiList = [];
67
+ let totalProfitWei = 0n;
68
+ for (let i = 0; i < ownerWallets.length; i++) {
69
+ const amt = String(params.buyAmountsOkb[i] || '0');
70
+ const originalBuyWei = ethers.parseEther(amt);
71
+ const profitWei = (originalBuyWei * BigInt(profitBps)) / 10000n;
72
+ const buyWei = originalBuyWei - profitWei;
73
+ buyWeiList.push(buyWei);
74
+ totalProfitWei += profitWei;
75
+ }
76
+ // 批量预测买入后获得的代币数量(用于卖出,避免 maxUint256 问题)
77
+ let expectedTokenAmounts = [];
78
+ if (poolType === 'flap') {
79
+ const portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
80
+ expectedTokenAmounts = await mapWithConcurrency(buyWeiList, 4, async (buyWei) => {
81
+ if (buyWei <= 0n)
82
+ return 0n;
83
+ try {
84
+ return await portalQuery.previewBuy(params.tokenAddress, buyWei);
85
+ }
86
+ catch {
87
+ try {
88
+ return await portalQuery.quoteExactInput('0x0000000000000000000000000000000000000000', params.tokenAddress, buyWei);
89
+ }
90
+ catch {
91
+ return 0n;
92
+ }
93
+ }
94
+ });
95
+ }
96
+ else if (poolType === 'v2') {
97
+ const dexQuery = new DexQuery({ rpcUrl: config.rpcUrl, routerAddress, wokbAddress: wokb });
98
+ expectedTokenAmounts = await mapWithConcurrency(buyWeiList, 4, async (buyWei) => {
99
+ if (buyWei <= 0n)
100
+ return 0n;
101
+ try {
102
+ return await dexQuery.quoteOkbToToken(buyWei, params.tokenAddress);
103
+ }
104
+ catch {
105
+ return 0n;
106
+ }
107
+ });
108
+ }
109
+ else if (poolType === 'v3') {
110
+ // V3 使用 Portal 的 quoteExactInput 作为 fallback(理想情况下应使用 Quoter 合约)
111
+ const portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
112
+ expectedTokenAmounts = await mapWithConcurrency(buyWeiList, 4, async (buyWei) => {
113
+ if (buyWei <= 0n)
114
+ return 0n;
115
+ try {
116
+ return await portalQuery.quoteExactInput(wokb, params.tokenAddress, buyWei);
117
+ }
118
+ catch {
119
+ return 0n;
120
+ }
121
+ });
122
+ }
123
+ console.log(`[buildWashOps] 预测买入代币数量 (${poolType}):`, expectedTokenAmounts.map(a => a.toString()));
124
+ // 构建所有 UserOps 的骨架
125
+ const allSkeletons = [];
126
+ for (let i = 0; i < ownerWallets.length; i++) {
127
+ const ownerWallet = ownerWallets[i];
128
+ const acc = accounts[i];
129
+ const amt = String(params.buyAmountsOkb[i] || '0');
130
+ const originalBuyWei = ethers.parseEther(amt);
131
+ const profitWei = (originalBuyWei * BigInt(profitBps)) / 10000n;
132
+ const buyWei = buyWeiList[i];
133
+ let currentNonce = BigInt(acc.nonce || 0);
134
+ const initCode = acc.deployed ? '0x' : aaManager.generateInitCode(ownerWallet.address);
135
+ // 1) 利润刮取 UserOp(强制执行)
136
+ if (profitWei > 0n) {
137
+ const profitCallData = encodeExecute(profitRecipient, profitWei, '0x');
138
+ allSkeletons.push({
139
+ ownerWallet,
140
+ sender: acc.sender,
141
+ nonce: currentNonce,
142
+ initCode: initCode, // 第一个 Op 需要 initCode
143
+ callData: profitCallData,
144
+ opType: 'profit',
145
+ });
146
+ currentNonce += 1n;
147
+ }
148
+ // 2) 买入 UserOp
149
+ let buyCallData;
150
+ let sellCallData;
151
+ if (poolType === 'flap') {
152
+ const buySwapData = encodeBuyCall(params.tokenAddress, buyWei, 0n);
153
+ buyCallData = encodeExecute(portal, buyWei, buySwapData);
154
+ const expectedTokenAmount = expectedTokenAmounts[i] || 0n;
155
+ const sellSwapData = encodeSellCall(params.tokenAddress, expectedTokenAmount, 0n);
156
+ sellCallData = encodeExecute(portal, 0n, sellSwapData);
157
+ }
158
+ else if (poolType === 'v3') {
159
+ const buySwapData = encodeSwapExactETHForTokensV3({
160
+ tokenIn: wokb,
161
+ tokenOut: params.tokenAddress,
162
+ fee: v3Fee,
163
+ recipient: acc.sender,
164
+ deadline,
165
+ amountIn: buyWei,
166
+ amountOutMinimum: 0n,
167
+ sqrtPriceLimitX96: 0n,
168
+ });
169
+ buyCallData = encodeExecute(routerAddress, buyWei, buySwapData);
170
+ const expectedTokenAmount = expectedTokenAmounts[i] || 0n;
171
+ const sellSwapData = encodeSwapExactTokensForETHV3({
172
+ tokenIn: params.tokenAddress,
173
+ tokenOut: wokb,
174
+ fee: v3Fee,
175
+ recipient: acc.sender,
176
+ unwrapRecipient: acc.sender,
177
+ deadline,
178
+ amountIn: expectedTokenAmount,
179
+ amountOutMinimum: 0n,
180
+ sqrtPriceLimitX96: 0n,
181
+ });
182
+ sellCallData = encodeExecute(routerAddress, 0n, sellSwapData);
183
+ }
184
+ else {
185
+ // V2
186
+ const buySwapData = encodeSwapExactETHForTokensSupportingFee(0n, [wokb, params.tokenAddress], acc.sender, deadline);
187
+ buyCallData = encodeExecute(routerAddress, buyWei, buySwapData);
188
+ const expectedTokenAmount = expectedTokenAmounts[i] || 0n;
189
+ const sellSwapData = encodeSwapExactTokensForETHSupportingFee(expectedTokenAmount, 0n, [params.tokenAddress, wokb], acc.sender, deadline);
190
+ sellCallData = encodeExecute(routerAddress, 0n, sellSwapData);
191
+ }
192
+ // 买入 Op 的 initCode:如果有利润 Op,则利润 Op 已处理部署
193
+ const buyInitCode = profitWei > 0n ? '0x' : initCode;
194
+ allSkeletons.push({
195
+ ownerWallet,
196
+ sender: acc.sender,
197
+ nonce: currentNonce,
198
+ initCode: buyInitCode,
199
+ callData: buyCallData,
200
+ opType: 'buy',
201
+ });
202
+ currentNonce += 1n;
203
+ // 3) 卖出 UserOp
204
+ allSkeletons.push({
205
+ ownerWallet,
206
+ sender: acc.sender,
207
+ nonce: currentNonce,
208
+ initCode: '0x', // 前面已经部署
209
+ callData: sellCallData,
210
+ opType: 'sell',
211
+ });
212
+ }
213
+ // 固定 Gas
214
+ const profitCallGasLimit = 120000n;
215
+ const tradeCallGasLimit = 400000n;
216
+ // 构造 UserOps
217
+ const built = await mapWithConcurrency(allSkeletons, 20, async (sk) => {
218
+ const callGasLimit = sk.opType === 'profit' ? profitCallGasLimit : tradeCallGasLimit;
219
+ const { userOp } = await aaManager.buildUserOpWithFixedGas({
220
+ ownerWallet: sk.ownerWallet,
221
+ sender: sk.sender,
222
+ callData: sk.callData,
223
+ nonce: sk.nonce,
224
+ initCode: sk.initCode,
225
+ deployed: String(sk.initCode) === '0x',
226
+ fixedGas: { callGasLimit },
227
+ });
228
+ return userOp;
229
+ });
230
+ // 签名
231
+ const opOwnerIndex = [];
232
+ for (const sk of allSkeletons) {
233
+ const ownerIdx = ownerWallets.findIndex(w => w.address === sk.ownerWallet.address);
234
+ opOwnerIndex.push(ownerIdx >= 0 ? ownerIdx : 0);
235
+ }
236
+ const signedOps = await mapWithConcurrency(built.map((op, i) => ({ op, ownerIdx: opOwnerIndex[i] })), 20, async ({ op, ownerIdx }) => {
237
+ const ownerWallet = ownerWallets[ownerIdx];
238
+ const signed = await aaManager.signUserOp(op, ownerWallet);
239
+ return signed.userOp;
240
+ });
241
+ const senders = [...new Set(allSkeletons.map(s => s.sender))];
242
+ // 统计
243
+ const profitOpCount = allSkeletons.filter(s => s.opType === 'profit').length;
244
+ const buyOpCount = allSkeletons.filter(s => s.opType === 'buy').length;
245
+ const sellOpCount = allSkeletons.filter(s => s.opType === 'sell').length;
246
+ console.log(`[buildWashOps] 构建 ${signedOps.length} 个 UserOps(${ownerWallets.length} 个钱包):`, `利润=${profitOpCount}, 买入=${buyOpCount}, 卖出=${sellOpCount}`, totalProfitWei > 0n ? `| 总利润=${ethers.formatEther(totalProfitWei)} OKB` : '');
247
+ return { ops: signedOps, senders, profitWei: totalProfitWei };
248
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.6.53",
3
+ "version": "1.6.55",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",