four-flap-meme-sdk 1.5.33 → 1.5.35

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.
@@ -0,0 +1,415 @@
1
+ /**
2
+ * XLayer AA Buy-First(内盘 / Flap Portal)
3
+ *
4
+ * 对齐 BSC buy-first 思路:
5
+ * - 买方先买入(多钱包随机拆分资金)
6
+ * - 卖方再卖出“等值 token”(卖方需要预持仓)
7
+ * - ✅ 利润:在“卖出后归集 OKB”阶段刮取(executeBatch 拆分给 profitRecipient + owner)
8
+ *
9
+ * 说明:
10
+ * - buy + sell 会放在同一笔 handleOps 内,保持“buy-first 原子”语义
11
+ * - withdraw 需要知道 sell 后的余额,因此单独再发一笔 handleOps
12
+ */
13
+ import { Contract, Interface, Wallet, ethers } from 'ethers';
14
+ import { AANonceMap } from './types.js';
15
+ import { ENTRYPOINT_ABI, DEFAULT_WITHDRAW_RESERVE, FLAP_PORTAL, } from './constants.js';
16
+ import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.js';
17
+ import { PortalQuery, encodeApproveCall, encodeBuyCall, encodeSellCall, parseOkb } from './portal-ops.js';
18
+ import { mapWithConcurrency } from '../utils/concurrency.js';
19
+ import { PROFIT_CONFIG } from '../utils/constants.js';
20
+ // 固定 gas(用于减少 RPC 与提高可控性)
21
+ const DEFAULT_CALL_GAS_LIMIT_BUY = 450000n;
22
+ const DEFAULT_CALL_GAS_LIMIT_SELL = 450000n;
23
+ const DEFAULT_CALL_GAS_LIMIT_APPROVE = 200000n;
24
+ const DEFAULT_CALL_GAS_LIMIT_WITHDRAW = 120000n;
25
+ function resolveProfitSettings(config) {
26
+ const extractProfit = config?.extractProfit !== false; // 默认 true
27
+ const profitBpsRaw = config?.profitBps ?? PROFIT_CONFIG.RATE_BPS_SWAP;
28
+ const profitBps = Math.max(0, Math.min(10000, Number(profitBpsRaw)));
29
+ const profitRecipient = config?.profitRecipient ?? PROFIT_CONFIG.RECIPIENT;
30
+ return { extractProfit, profitBps, profitRecipient };
31
+ }
32
+ function calculateProfitWei(amountWei, profitBps) {
33
+ if (amountWei <= 0n)
34
+ return 0n;
35
+ if (!Number.isFinite(profitBps) || profitBps <= 0)
36
+ return 0n;
37
+ return (amountWei * BigInt(profitBps)) / 10000n;
38
+ }
39
+ function shuffle(arr) {
40
+ const a = arr.slice();
41
+ for (let i = a.length - 1; i > 0; i--) {
42
+ const j = Math.floor(Math.random() * (i + 1));
43
+ [a[i], a[j]] = [a[j], a[i]];
44
+ }
45
+ return a;
46
+ }
47
+ /**
48
+ * ✅ 将金额随机拆分成多份(BigInt 安全)
49
+ * 每份权重在 0.5 - 1.5 之间随机浮动(用整数权重近似)
50
+ */
51
+ function splitAmount(totalAmount, count) {
52
+ if (count <= 0)
53
+ throw new Error('拆分份数必须大于 0');
54
+ if (count === 1)
55
+ return [totalAmount];
56
+ if (totalAmount <= 0n)
57
+ return new Array(count).fill(0n);
58
+ const SCALE = 1000000n;
59
+ const weights = [];
60
+ for (let i = 0; i < count; i++) {
61
+ // [0.5, 1.5) * SCALE
62
+ const w = BigInt(500000 + Math.floor(Math.random() * 1000000));
63
+ weights.push(w);
64
+ }
65
+ const totalWeight = weights.reduce((a, b) => a + b, 0n);
66
+ const amounts = [];
67
+ let allocated = 0n;
68
+ for (let i = 0; i < count - 1; i++) {
69
+ const amt = (totalAmount * weights[i]) / totalWeight;
70
+ amounts.push(amt);
71
+ allocated += amt;
72
+ }
73
+ amounts.push(totalAmount - allocated);
74
+ return shuffle(amounts);
75
+ }
76
+ export class AAPortalBuyFirstExecutor {
77
+ constructor(config = {}) {
78
+ this.config = config;
79
+ this.aaManager = new AAAccountManager(config);
80
+ this.portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
81
+ }
82
+ async runHandleOps(label, ops, bundlerSigner, beneficiary) {
83
+ const provider = this.aaManager.getProvider();
84
+ const entryPointAddress = this.aaManager.getEntryPointAddress();
85
+ const feeData = await provider.getFeeData();
86
+ console.log(`\n[${label}] 发送 handleOps,ops=${ops.length} ...`);
87
+ const entryPointWithSigner = new Contract(entryPointAddress, ENTRYPOINT_ABI, bundlerSigner);
88
+ const tx = await entryPointWithSigner.handleOps(ops, beneficiary, { gasPrice: feeData.gasPrice ?? 100000000n });
89
+ console.log(`[${label}] txHash:`, tx.hash);
90
+ const receipt = await tx.wait();
91
+ console.log(`[${label}] mined block=${receipt.blockNumber} status=${receipt.status}`);
92
+ const epIface = new Interface(ENTRYPOINT_ABI);
93
+ const userOpEvents = [];
94
+ for (const log of receipt.logs) {
95
+ try {
96
+ const parsed = epIface.parseLog(log);
97
+ if (parsed?.name === 'UserOperationEvent') {
98
+ const e = parsed.args;
99
+ userOpEvents.push({
100
+ userOpHash: e.userOpHash,
101
+ sender: e.sender,
102
+ paymaster: e.paymaster,
103
+ success: e.success,
104
+ actualGasCost: e.actualGasCost,
105
+ actualGasUsed: e.actualGasUsed,
106
+ });
107
+ }
108
+ }
109
+ catch { }
110
+ }
111
+ return { txHash: tx.hash, blockNumber: receipt.blockNumber, status: receipt.status, userOpEvents };
112
+ }
113
+ pickWallets(privateKeys, n) {
114
+ const provider = this.aaManager.getProvider();
115
+ const wallets = privateKeys.map((pk) => new Wallet(pk, provider));
116
+ if (!n || n <= 0 || n >= wallets.length)
117
+ return wallets;
118
+ return wallets.slice(0, n);
119
+ }
120
+ async getAccountInfos(wallets) {
121
+ return await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
122
+ }
123
+ async safePreviewBuy(tokenAddress, okbAmount) {
124
+ if (okbAmount <= 0n)
125
+ return 0n;
126
+ try {
127
+ return await this.portalQuery.previewBuy(tokenAddress, okbAmount);
128
+ }
129
+ catch {
130
+ // fallback:quoteExactInput(native->token)
131
+ return await this.portalQuery.quoteExactInput('0x0000000000000000000000000000000000000000', tokenAddress, okbAmount);
132
+ }
133
+ }
134
+ async buildWithdrawUserOp(params) {
135
+ const senderBalance = params.senderBalance;
136
+ if (senderBalance <= params.reserveWei)
137
+ return null;
138
+ const effConfig = { ...(this.config ?? {}), ...(params.configOverride ?? {}) };
139
+ const gasPolicyRaw = effConfig.gasPolicy ?? 'bundlerEstimate';
140
+ const gasPolicy = gasPolicyRaw === 'bundlerEstimate' ? 'fixed' : gasPolicyRaw;
141
+ // 先估 prefund(空调用)
142
+ const tempCallData = encodeExecute(params.ownerWallet.address, 0n, '0x');
143
+ const { prefundWei } = gasPolicy === 'fixed'
144
+ ? await this.aaManager.buildUserOpWithFixedGas({
145
+ ownerWallet: params.ownerWallet,
146
+ sender: params.sender,
147
+ callData: tempCallData,
148
+ nonce: params.nonce,
149
+ initCode: params.initCode,
150
+ deployed: params.initCode === '0x',
151
+ fixedGas: {
152
+ ...(effConfig.fixedGas ?? {}),
153
+ callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
154
+ },
155
+ })
156
+ : await this.aaManager.buildUserOpWithLocalEstimate({
157
+ ownerWallet: params.ownerWallet,
158
+ sender: params.sender,
159
+ callData: tempCallData,
160
+ nonce: params.nonce,
161
+ initCode: params.initCode,
162
+ });
163
+ const withdrawAmount = senderBalance > prefundWei + params.reserveWei
164
+ ? senderBalance - prefundWei - params.reserveWei
165
+ : 0n;
166
+ if (withdrawAmount <= 0n)
167
+ return null;
168
+ const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effConfig);
169
+ const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
170
+ const toOwnerWei = withdrawAmount - profitWei;
171
+ const callData = extractProfit && profitWei > 0n
172
+ ? encodeExecuteBatch([profitRecipient, params.ownerWallet.address], [profitWei, toOwnerWei], ['0x', '0x'])
173
+ : encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
174
+ const { userOp } = gasPolicy === 'fixed'
175
+ ? await this.aaManager.buildUserOpWithFixedGas({
176
+ ownerWallet: params.ownerWallet,
177
+ sender: params.sender,
178
+ callData,
179
+ nonce: params.nonce,
180
+ initCode: params.initCode,
181
+ deployed: params.initCode === '0x',
182
+ fixedGas: {
183
+ ...(effConfig.fixedGas ?? {}),
184
+ callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
185
+ },
186
+ })
187
+ : await this.aaManager.buildUserOpWithLocalEstimate({
188
+ ownerWallet: params.ownerWallet,
189
+ sender: params.sender,
190
+ callData,
191
+ nonce: params.nonce,
192
+ initCode: params.initCode,
193
+ });
194
+ return { userOp, prefundWei, profitWei };
195
+ }
196
+ /**
197
+ * 执行一轮 buy-first(内盘)
198
+ */
199
+ async execute(params) {
200
+ const { tradeType = 'FLAP', tokenAddress, buyerPrivateKeys, sellerPrivateKeys, buyerFunds, buyCount, sellCount, slippageBps = 0, withdrawToOwner = true, withdrawReserve = DEFAULT_WITHDRAW_RESERVE, config, } = params;
201
+ if (tradeType !== 'FLAP') {
202
+ throw new Error('AAPortalBuyFirstExecutor 仅支持 tradeType=FLAP(内盘)');
203
+ }
204
+ const buyers = this.pickWallets(buyerPrivateKeys, buyCount);
205
+ const sellers = this.pickWallets(sellerPrivateKeys, sellCount);
206
+ if (buyers.length === 0)
207
+ throw new Error('buyCount=0 或 buyerPrivateKeys 为空');
208
+ if (sellers.length === 0)
209
+ throw new Error('sellCount=0 或 sellerPrivateKeys 为空');
210
+ const effConfig = { ...(this.config ?? {}), ...(config ?? {}) };
211
+ const profitSettings = resolveProfitSettings(effConfig);
212
+ const totalBuyWei = parseOkb(String(buyerFunds));
213
+ if (totalBuyWei <= 0n)
214
+ throw new Error('buyerFunds 需要 > 0');
215
+ const buyAmountsWei = splitAmount(totalBuyWei, buyers.length);
216
+ // 估算买入 tokenOut,并按 slippageBps 做折减(只用于估算 sellAmount)
217
+ const quotedPerBuy = await mapWithConcurrency(buyAmountsWei, 6, async (amt) => this.safePreviewBuy(tokenAddress, amt));
218
+ const quotedTotalTokenOut = quotedPerBuy.reduce((a, b) => a + (b ?? 0n), 0n);
219
+ const estimatedTokenOutWei = (quotedTotalTokenOut * BigInt(10000 - Math.max(0, Math.min(10000, slippageBps)))) / 10000n;
220
+ if (estimatedTokenOutWei <= 0n) {
221
+ throw new Error('买入报价为 0,无法规划 sellAmount(可能无流动性或报价失败)');
222
+ }
223
+ const sellAmountsWei = splitAmount(estimatedTokenOutWei, sellers.length);
224
+ // 获取 AA 账户信息
225
+ const buyerAis = await this.getAccountInfos(buyers);
226
+ const sellerAis = await this.getAccountInfos(sellers);
227
+ // nonceMap
228
+ const nonceMap = new AANonceMap();
229
+ for (const ai of buyerAis)
230
+ nonceMap.init(ai.sender, ai.nonce);
231
+ for (const ai of sellerAis)
232
+ nonceMap.init(ai.sender, ai.nonce);
233
+ // initCode:确保同一 sender 只在第一笔 op 携带
234
+ const initCodeBySenderLower = new Map();
235
+ const initIfNeeded = (sender, deployed, ownerAddress) => {
236
+ const k = sender.toLowerCase();
237
+ if (initCodeBySenderLower.has(k))
238
+ return;
239
+ initCodeBySenderLower.set(k, deployed ? '0x' : this.aaManager.generateInitCode(ownerAddress));
240
+ };
241
+ const consumeInitCode = (sender) => {
242
+ const k = sender.toLowerCase();
243
+ const cur = initCodeBySenderLower.get(k);
244
+ if (cur === undefined)
245
+ throw new Error(`initCode not initialized for sender: ${sender}`);
246
+ if (cur !== '0x')
247
+ initCodeBySenderLower.set(k, '0x');
248
+ return cur;
249
+ };
250
+ for (let i = 0; i < buyers.length; i++)
251
+ initIfNeeded(buyerAis[i].sender, buyerAis[i].deployed, buyers[i].address);
252
+ for (let i = 0; i < sellers.length; i++)
253
+ initIfNeeded(sellerAis[i].sender, sellerAis[i].deployed, sellers[i].address);
254
+ // 卖方余额检查(需要预持仓)
255
+ const sellerSenders = sellerAis.map((ai) => ai.sender);
256
+ const sellerTokenBalances = await this.portalQuery.getMultipleTokenBalances(tokenAddress, sellerSenders);
257
+ for (let i = 0; i < sellers.length; i++) {
258
+ const sender = sellerSenders[i];
259
+ const needSell = sellAmountsWei[i] ?? 0n;
260
+ const bal = sellerTokenBalances.get(sender) ?? 0n;
261
+ if (needSell > bal) {
262
+ throw new Error(`卖方预持仓不足: sender=${sender} need=${needSell.toString()} bal=${bal.toString()}`);
263
+ }
264
+ }
265
+ // 卖方 allowance 检查(spender=FLAP_PORTAL)
266
+ const sellerAllowances = await this.portalQuery.getMultipleAllowances(tokenAddress, sellerSenders, FLAP_PORTAL);
267
+ // 构建 ops(buy-first:buy -> (approve) -> sell)
268
+ const ops = [];
269
+ // 1) buy ops(多买方)
270
+ for (let i = 0; i < buyers.length; i++) {
271
+ const w = buyers[i];
272
+ const ai = buyerAis[i];
273
+ const sender = ai.sender;
274
+ const buyWei = buyAmountsWei[i] ?? 0n;
275
+ if (buyWei <= 0n)
276
+ continue;
277
+ const swapData = encodeBuyCall(tokenAddress, buyWei, 0n);
278
+ const callData = encodeExecute(FLAP_PORTAL, buyWei, swapData);
279
+ const { userOp, prefundWei } = await this.aaManager.buildUserOpWithFixedGas({
280
+ ownerWallet: w,
281
+ sender,
282
+ nonce: nonceMap.next(sender),
283
+ initCode: consumeInitCode(sender),
284
+ callData,
285
+ deployed: ai.deployed,
286
+ fixedGas: {
287
+ ...(effConfig.fixedGas ?? {}),
288
+ callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_BUY,
289
+ },
290
+ });
291
+ // 确保 sender 有足够余额:买入金额 + prefund(paymaster 下 prefund=0)
292
+ await this.aaManager.ensureSenderBalance(w, sender, buyWei + prefundWei + parseOkb('0.0001'), `buyFirst/buyer${i + 1}/fund`);
293
+ const signed = await this.aaManager.signUserOp(userOp, w);
294
+ ops.push(signed.userOp);
295
+ }
296
+ // 2) seller approve + sell ops
297
+ for (let i = 0; i < sellers.length; i++) {
298
+ const w = sellers[i];
299
+ const ai = sellerAis[i];
300
+ const sender = ai.sender;
301
+ const sellWei = sellAmountsWei[i] ?? 0n;
302
+ if (sellWei <= 0n)
303
+ continue;
304
+ const allowance = sellerAllowances.get(sender) ?? 0n;
305
+ if (allowance < sellWei) {
306
+ const approveCallData = encodeExecute(tokenAddress, 0n, encodeApproveCall(FLAP_PORTAL, ethers.MaxUint256));
307
+ const { userOp: approveOp, prefundWei } = await this.aaManager.buildUserOpWithFixedGas({
308
+ ownerWallet: w,
309
+ sender,
310
+ nonce: nonceMap.next(sender),
311
+ initCode: consumeInitCode(sender),
312
+ callData: approveCallData,
313
+ deployed: ai.deployed,
314
+ fixedGas: {
315
+ ...(effConfig.fixedGas ?? {}),
316
+ callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_APPROVE,
317
+ },
318
+ });
319
+ await this.aaManager.ensureSenderBalance(w, sender, prefundWei + parseOkb('0.0001'), `buyFirst/seller${i + 1}/approve-fund`);
320
+ const signedApprove = await this.aaManager.signUserOp(approveOp, w);
321
+ ops.push(signedApprove.userOp);
322
+ }
323
+ const sellSwapData = encodeSellCall(tokenAddress, sellWei, 0n);
324
+ const sellCallData = encodeExecute(FLAP_PORTAL, 0n, sellSwapData);
325
+ const { userOp: sellOp, prefundWei: sellPrefund } = await this.aaManager.buildUserOpWithFixedGas({
326
+ ownerWallet: w,
327
+ sender,
328
+ nonce: nonceMap.next(sender),
329
+ initCode: consumeInitCode(sender),
330
+ callData: sellCallData,
331
+ deployed: ai.deployed,
332
+ fixedGas: {
333
+ ...(effConfig.fixedGas ?? {}),
334
+ callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_SELL,
335
+ },
336
+ });
337
+ await this.aaManager.ensureSenderBalance(w, sender, sellPrefund + parseOkb('0.0001'), `buyFirst/seller${i + 1}/sell-fund`);
338
+ const signedSell = await this.aaManager.signUserOp(sellOp, w);
339
+ ops.push(signedSell.userOp);
340
+ }
341
+ if (ops.length === 0) {
342
+ throw new Error('本轮没有生成任何 UserOp(buyAmounts 或 sellAmounts 可能全为 0)');
343
+ }
344
+ // payer/beneficiary
345
+ const payerWallet = buyers[0];
346
+ const beneficiary = payerWallet.address;
347
+ // 第一步:buy-first 原子阶段
348
+ const buySellResult = await this.runHandleOps('aa-buy-first/portal', ops, payerWallet, beneficiary);
349
+ // 第二步:可选归集(卖方)
350
+ let withdrawResult;
351
+ let finalSellerBalances;
352
+ let totalProfitWei = 0n;
353
+ if (withdrawToOwner) {
354
+ const reserveWei = parseOkb(withdrawReserve);
355
+ const sellerOkbBalances = await this.portalQuery.getMultipleOkbBalances(sellerSenders);
356
+ const withdrawOps = [];
357
+ const withdrawOpsBuilt = await mapWithConcurrency(sellers, 3, async (w, i) => {
358
+ const sender = sellerAis[i].sender;
359
+ const senderBalance = sellerOkbBalances.get(sender) ?? 0n;
360
+ const usedNonce = nonceMap.peek(sender);
361
+ const built = await this.buildWithdrawUserOp({
362
+ ownerWallet: w,
363
+ sender,
364
+ nonce: usedNonce,
365
+ initCode: '0x',
366
+ senderBalance,
367
+ reserveWei,
368
+ configOverride: config,
369
+ });
370
+ if (!built)
371
+ return null;
372
+ nonceMap.commit(sender, usedNonce);
373
+ totalProfitWei += built.profitWei;
374
+ const signed = await this.aaManager.signUserOp(built.userOp, w);
375
+ return signed.userOp;
376
+ });
377
+ for (const op of withdrawOpsBuilt)
378
+ if (op)
379
+ withdrawOps.push(op);
380
+ if (withdrawOps.length > 0) {
381
+ withdrawResult = await this.runHandleOps('aa-buy-first/portal-withdraw', withdrawOps, payerWallet, beneficiary);
382
+ }
383
+ finalSellerBalances = await this.portalQuery.getMultipleOkbBalances(sellerSenders);
384
+ }
385
+ return {
386
+ buySellResult,
387
+ withdrawResult,
388
+ finalSellerBalances,
389
+ profit: withdrawToOwner
390
+ ? {
391
+ extractProfit: profitSettings.extractProfit,
392
+ profitBps: profitSettings.profitBps,
393
+ profitRecipient: profitSettings.profitRecipient,
394
+ totalProfitWei: totalProfitWei.toString(),
395
+ }
396
+ : undefined,
397
+ metadata: {
398
+ tradeType: 'FLAP',
399
+ tokenAddress,
400
+ buyerOwners: buyers.map((w) => w.address),
401
+ buyerSenders: buyerAis.map((ai) => ai.sender),
402
+ sellerOwners: sellers.map((w) => w.address),
403
+ sellerSenders,
404
+ buyAmountsWei: buyAmountsWei.map((x) => x.toString()),
405
+ sellAmountsWei: sellAmountsWei.map((x) => x.toString()),
406
+ totalBuyWei: totalBuyWei.toString(),
407
+ estimatedTokenOutWei: estimatedTokenOutWei.toString(),
408
+ slippageBps,
409
+ },
410
+ };
411
+ }
412
+ }
413
+ export function createAAPortalBuyFirstExecutor(config) {
414
+ return new AAPortalBuyFirstExecutor(config);
415
+ }
@@ -161,8 +161,12 @@ export interface BundleBuyParams {
161
161
  tokenAddress: string;
162
162
  /** Owner 私钥列表 */
163
163
  privateKeys: string[];
164
- /** 每个 owner 的买入金额(OKB */
164
+ /** 每个 owner 的买入金额(OKB 或 quoteToken,取决于 quoteToken 参数) */
165
165
  buyAmounts: string[];
166
+ /** 买入使用的代币地址(零地址表示使用原生代币 OKB,非零地址表示使用 ERC20 代币如 USDC) */
167
+ quoteToken?: string;
168
+ /** quoteToken 的精度(默认 18,如果 quoteToken 不是 18 位精度需要指定) */
169
+ quoteTokenDecimals?: number;
166
170
  /** 是否将代币转回 owner EOA(默认 false) */
167
171
  transferBackToOwner?: boolean;
168
172
  /** 配置覆盖 */
@@ -193,8 +197,12 @@ export interface BundleBuySellParams {
193
197
  tokenAddress: string;
194
198
  /** Owner 私钥列表 */
195
199
  privateKeys: string[];
196
- /** 每个 owner 的买入金额(OKB */
200
+ /** 每个 owner 的买入金额(OKB 或 quoteToken,取决于 quoteToken 参数) */
197
201
  buyAmounts: string[];
202
+ /** 买入使用的代币地址(零地址表示使用原生代币 OKB,非零地址表示使用 ERC20 代币如 USDC) */
203
+ quoteToken?: string;
204
+ /** quoteToken 的精度(默认 18,如果 quoteToken 不是 18 位精度需要指定) */
205
+ quoteTokenDecimals?: number;
198
206
  /** 卖出比例(0-100,默认 100 全部卖出) */
199
207
  sellPercent?: number;
200
208
  /** 是否将 OKB 归集回 owner EOA(默认 true) */
@@ -528,6 +536,64 @@ export interface VolumeResult {
528
536
  /** 总交易量(OKB) */
529
537
  totalVolume: bigint;
530
538
  }
539
+ export type BuyFirstTradeType = 'FLAP' | 'V2';
540
+ export interface BuyFirstParams {
541
+ tradeType?: BuyFirstTradeType;
542
+ /** DEX 标识(仅 V2 时使用,用于选择 Router) */
543
+ dexKey?: string;
544
+ /** 显式指定 Router 地址(仅 V2 时使用) */
545
+ routerAddress?: string;
546
+ /** DEX deadline 分钟数(仅 V2 时使用) */
547
+ deadlineMinutes?: number;
548
+ tokenAddress: string;
549
+ buyerPrivateKeys: string[];
550
+ sellerPrivateKeys: string[];
551
+ /** 本轮总买入资金(OKB) */
552
+ buyerFunds: string;
553
+ buyCount?: number;
554
+ sellCount?: number;
555
+ /** 报价滑点(仅用于估算 sellAmount),默认 0 */
556
+ slippageBps?: number;
557
+ withdrawToOwner?: boolean;
558
+ withdrawReserve?: string;
559
+ config?: Partial<XLayerConfig>;
560
+ }
561
+ export interface BuyFirstResult {
562
+ buySellResult: HandleOpsResult;
563
+ withdrawResult?: HandleOpsResult;
564
+ finalSellerBalances?: Map<string, bigint>;
565
+ profit?: {
566
+ extractProfit: boolean;
567
+ profitBps: number;
568
+ profitRecipient: string;
569
+ totalProfitWei: string;
570
+ };
571
+ metadata: {
572
+ tradeType: BuyFirstTradeType;
573
+ tokenAddress: string;
574
+ buyerOwners: string[];
575
+ buyerSenders: string[];
576
+ sellerOwners: string[];
577
+ sellerSenders: string[];
578
+ buyAmountsWei: string[];
579
+ sellAmountsWei: string[];
580
+ totalBuyWei: string;
581
+ estimatedTokenOutWei: string;
582
+ slippageBps: number;
583
+ };
584
+ }
585
+ export interface BuyFirstVolumeParams extends Omit<BuyFirstParams, 'buyerFunds'> {
586
+ buyerFundsPerRound: string;
587
+ rounds: number;
588
+ intervalMs?: number;
589
+ }
590
+ export interface BuyFirstVolumeResult {
591
+ successRounds: number;
592
+ failedRounds: number;
593
+ roundResults: BuyFirstResult[];
594
+ /** 总交易量(OKB wei,按“买入资金*2”统计) */
595
+ totalVolume: bigint;
596
+ }
531
597
  /**
532
598
  * 外盘(PotatoSwap)交易参数
533
599
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.5.33",
3
+ "version": "1.5.35",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,4 +36,4 @@
36
36
  "devDependencies": {
37
37
  "typescript": "^5.6.3"
38
38
  }
39
- }
39
+ }