four-flap-meme-sdk 1.7.60 → 1.7.62

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.
@@ -315,20 +315,37 @@ export async function bundleSwap(params) {
315
315
  ? calculateProfitAmountByUserType(estimatedOkbOut, userType, true) // isDoubleMode = true
316
316
  : 0n;
317
317
  const profitRecipient = getProfitRecipient(config);
318
+ // ✅ 计算买入金额:基于卖家卖出得到的 OKB(不是买家全部余额)
319
+ const buyAmountAfterProfit = estimatedOkbOut > profitAmount
320
+ ? estimatedOkbOut - profitAmount
321
+ : 0n;
318
322
  console.log(`[Bundle Swap] 卖出数量: ${sellAmount} Token`);
319
323
  console.log(`[Bundle Swap] 预估获得: ${ethers.formatEther(estimatedOkbOut)} OKB`);
320
324
  console.log(`[Bundle Swap] 利润: ${ethers.formatEther(profitAmount)} OKB (userType=${userType})`);
325
+ console.log(`[Bundle Swap] 买家应买入: ${ethers.formatEther(buyAmountAfterProfit)} OKB(基于卖出所得)`);
321
326
  // ========================================
322
327
  // 并行获取所有异步数据
323
328
  // ========================================
324
- const [nonces, feeData] = await Promise.all([
329
+ const [nonces, feeData, buyerBalance] = await Promise.all([
325
330
  // 并行获取 seller 和 buyer 的 nonce
326
331
  batchGetNonces([sellerWallet.address, buyerWallet.address], provider),
327
332
  // 获取 gas 费用数据
328
333
  provider.getFeeData(),
334
+ // 获取买家的 OKB 余额(验证是否足够)
335
+ provider.getBalance(buyerWallet.address),
329
336
  ]);
330
337
  const sellerNonce = nonces[0];
331
338
  const buyerNonce = nonces[1];
339
+ console.log(`[Bundle Swap] 买家 OKB 余额: ${ethers.formatEther(buyerBalance)} OKB`);
340
+ // ✅ FLAP 模式需要买家有足够的 OKB(至少等于卖家卖出得到的 OKB)
341
+ if (tradeType === 'FLAP') {
342
+ if (buyAmountAfterProfit <= 0n) {
343
+ throw new Error('FLAP 内盘模式需要有效的报价,请检查代币地址或稍后重试');
344
+ }
345
+ if (buyerBalance < buyAmountAfterProfit) {
346
+ throw new Error(`FLAP 内盘模式需要买家钱包有足够的 OKB,需要 ${ethers.formatEther(buyAmountAfterProfit)},当前 ${ethers.formatEther(buyerBalance)}`);
347
+ }
348
+ }
332
349
  // ========================================
333
350
  // 同步签署授权(只需要卖家和买家,不需要 hop wallets)
334
351
  // ========================================
@@ -339,21 +356,11 @@ export async function bundleSwap(params) {
339
356
  authorizations.push(signAuthorization(buyerWallet, delegateAddress, BigInt(buyerNonce)));
340
357
  // 等待所有授权签名完成
341
358
  const resolvedAuthorizations = await Promise.all(authorizations);
342
- // ========================================
343
- // 同步构建调用
344
- // ========================================
345
- // ✅ 计算买入金额
346
- const buyAmount = estimatedOkbOut > profitAmount ? estimatedOkbOut - profitAmount : 0n;
347
- console.log(`[Bundle Swap] 预计买入金额: ${ethers.formatEther(buyAmount)} OKB`);
348
- // ✅ FLAP 模式需要有效的报价(不能使用 0n 作为买入金额)
349
- if (tradeType === 'FLAP' && estimatedOkbOut <= 0n) {
350
- throw new Error('FLAP 内盘模式需要有效的报价,请检查代币地址或稍后重试');
351
- }
352
359
  const calls = [];
353
360
  // ========================================
354
- // ✅ 修复:普通换手模式逻辑
361
+ // ✅ 普通换手模式逻辑
355
362
  // 1. 卖家卖出代币 → OKB 留在卖家账户
356
- // 2. 买家用自己预存的 OKB 买入代币
363
+ // 2. 买家用自己预存的 OKB 买入代币(金额 = 卖家卖出所得,不是全部余额)
357
364
  // 3. 利润从买家的 OKB 中扣除
358
365
  // ========================================
359
366
  // 1. 卖家卖出(OKB 留在卖家账户,不转给买家)
@@ -362,15 +369,17 @@ export async function bundleSwap(params) {
362
369
  // 2. 利润刮取(从买家余额扣除,在买入之前)
363
370
  if (profitAmount > 0n) {
364
371
  calls.push({
365
- target: buyerWallet.address, // ✅ 改为从买家扣利润
372
+ target: buyerWallet.address,
366
373
  allowFailure: false,
367
374
  value: 0n,
368
375
  callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
369
376
  });
370
377
  }
371
378
  // 3. 买家用自己预存的 OKB 买入
372
- // ✅ FLAP 模式必须使用实际金额,V2/V3 模式使用 0n(钱包全部余额)
373
- const buyAmountForCall = tradeType === 'FLAP' ? buyAmount : 0n;
379
+ // ✅ 关键修复:买入金额 = 卖家卖出所得(扣除利润),而不是买家全部余额
380
+ // FLAP 模式必须指定精确金额,V2/V3 使用 0n(合约自动使用全部余额)
381
+ const buyAmountForCall = tradeType === 'FLAP' ? buyAmountAfterProfit : 0n;
382
+ console.log(`[Bundle Swap] 实际买入调用金额: ${ethers.formatEther(buyAmountForCall)} OKB`);
374
383
  const buyCall = buildBuyCallInternal(buyerWallet, buyAmountForCall, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
375
384
  calls.push(buyCall);
376
385
  // ========================================
@@ -432,23 +441,38 @@ export async function bundleBatchSwap(params) {
432
441
  ? calculateProfitAmountByUserType(estimatedOkbOut, userType, true) // isDoubleMode = true
433
442
  : 0n;
434
443
  const profitRecipient = getProfitRecipient(config);
444
+ // ✅ 计算总买入金额:基于卖家卖出得到的 OKB(不是买家全部余额)
445
+ const totalBuyAmountAfterProfit = estimatedOkbOut > profitAmount
446
+ ? estimatedOkbOut - profitAmount
447
+ : 0n;
435
448
  console.log(`[Bundle Batch Swap] 卖出数量: ${sellAmount} Token`);
436
449
  console.log(`[Bundle Batch Swap] 预估获得: ${ethers.formatEther(estimatedOkbOut)} OKB`);
437
450
  console.log(`[Bundle Batch Swap] 利润: ${ethers.formatEther(profitAmount)} OKB (userType=${userType})`);
451
+ console.log(`[Bundle Batch Swap] 买家总应买入: ${ethers.formatEther(totalBuyAmountAfterProfit)} OKB(基于卖出所得)`);
438
452
  // ✅ FLAP 模式需要有效的报价(不能使用 0n 作为买入金额)
439
- if (tradeType === 'FLAP' && estimatedOkbOut <= 0n) {
453
+ if (tradeType === 'FLAP' && totalBuyAmountAfterProfit <= 0n) {
440
454
  throw new Error('FLAP 内盘模式需要有效的报价,请检查代币地址或稍后重试');
441
455
  }
442
456
  // ========================================
443
457
  // 并行获取所有异步数据
444
458
  // ========================================
445
459
  const addressesToQuery = [sellerWallet.address, ...buyerWallets.map(w => w.address)];
446
- const [nonces, feeData] = await Promise.all([
460
+ const [nonces, feeData, ...buyerBalances] = await Promise.all([
447
461
  batchGetNonces(addressesToQuery, provider),
448
462
  provider.getFeeData(),
463
+ // 获取所有买家的 OKB 余额(验证是否足够)
464
+ ...buyerWallets.map(w => provider.getBalance(w.address)),
449
465
  ]);
450
466
  const sellerNonce = nonces[0];
451
467
  const buyerNonces = nonces.slice(1);
468
+ console.log(`[Bundle Batch Swap] 买家余额:`, buyerBalances.map(b => ethers.formatEther(b)));
469
+ // ✅ FLAP 模式验证:买家总余额必须足够
470
+ if (tradeType === 'FLAP') {
471
+ const totalBuyerBalance = buyerBalances.reduce((sum, b) => sum + BigInt(b.toString()), 0n);
472
+ if (totalBuyerBalance < totalBuyAmountAfterProfit) {
473
+ throw new Error(`FLAP 内盘模式需要买家钱包总余额足够,需要 ${ethers.formatEther(totalBuyAmountAfterProfit)},当前总计 ${ethers.formatEther(totalBuyerBalance)}`);
474
+ }
475
+ }
452
476
  // ========================================
453
477
  // 同步构建调用
454
478
  // ========================================
@@ -467,35 +491,51 @@ export async function bundleBatchSwap(params) {
467
491
  // 2. 利润刮取(从第一个买家扣除,在买入之前)
468
492
  if (profitAmount > 0n) {
469
493
  calls.push({
470
- target: buyerWallets[0].address, // ✅ 改为从第一个买家扣利润
494
+ target: buyerWallets[0].address,
471
495
  allowFailure: false,
472
496
  value: 0n,
473
497
  callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
474
498
  });
475
499
  }
476
500
  // ✅ 3. 计算每个买家的买入金额
477
- // 买家使用自己预存的 OKB,按比例买入对应价值的代币
478
- // 如果前端传入了 buyerRatios,按比例计算;否则均分
501
+ // 关键修复:买入金额 = 卖家卖出所得(按比例分配),而不是买家全部余额
479
502
  let buyAmountsPerBuyer;
480
- if (buyerWallets.length === 1) {
481
- // 单买家:使用 0n 表示用全部余额买入
482
- buyAmountsPerBuyer = [0n];
483
- }
484
- else if (buyerRatios && buyerRatios.length === buyerWallets.length) {
485
- // 多买家 + 有比例:每个买家使用 0n(用自己全部余额买入)
486
- // 前端应该根据比例给每个买家预存对应金额的 OKB
487
- buyAmountsPerBuyer = buyerWallets.map(() => 0n);
488
- console.log(`[bundleBatchSwap] 按比例买入模式,买家将使用自己预存的 OKB 买入`);
503
+ if (tradeType === 'FLAP') {
504
+ // FLAP 模式:按比例分配卖家卖出所得的 OKB
505
+ if (buyerWallets.length === 1) {
506
+ // 单买家:使用全部卖出所得
507
+ buyAmountsPerBuyer = [totalBuyAmountAfterProfit];
508
+ }
509
+ else if (buyerRatios && buyerRatios.length === buyerWallets.length) {
510
+ // 多买家 + 有比例:按前端传入的比例分配
511
+ let allocated = 0n;
512
+ buyAmountsPerBuyer = buyerRatios.map((ratio, i) => {
513
+ const amount = (totalBuyAmountAfterProfit * BigInt(Math.round(ratio * 10000))) / 10000n;
514
+ if (i === buyerRatios.length - 1) {
515
+ return totalBuyAmountAfterProfit - allocated; // 最后一个分配剩余,避免精度问题
516
+ }
517
+ allocated += amount;
518
+ return amount;
519
+ });
520
+ }
521
+ else {
522
+ // 多买家无比例:均分
523
+ const avgAmount = totalBuyAmountAfterProfit / BigInt(buyerWallets.length);
524
+ const remainder = totalBuyAmountAfterProfit % BigInt(buyerWallets.length);
525
+ buyAmountsPerBuyer = buyerWallets.map((_, i) => {
526
+ // 最后一个买家分配剩余
527
+ return i === buyerWallets.length - 1 ? avgAmount + remainder : avgAmount;
528
+ });
529
+ }
530
+ console.log(`[bundleBatchSwap] FLAP 模式,买家买入金额(基于卖出所得):`, buyAmountsPerBuyer.map(a => ethers.formatEther(a)));
489
531
  }
490
532
  else {
491
- // 多买家无比例:每个买家使用全部余额买入
533
+ // V2/V3 模式:使用 0n,合约自动使用全部余额
492
534
  buyAmountsPerBuyer = buyerWallets.map(() => 0n);
493
535
  }
494
536
  // 4. 所有买家买入(使用自己预存的 OKB)
495
537
  for (let i = 0; i < buyerWallets.length; i++) {
496
- // FLAP 模式使用分配的金额(0n = 用全部余额),V2/V3 模式也使用 0n
497
- const buyAmountForCall = tradeType === 'FLAP' ? buyAmountsPerBuyer[i] : 0n;
498
- const buyCall = buildBuyCallInternal(buyerWallets[i], buyAmountForCall, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
538
+ const buyCall = buildBuyCallInternal(buyerWallets[i], buyAmountsPerBuyer[i], tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
499
539
  calls.push(buyCall);
500
540
  }
501
541
  // ========================================
@@ -21,8 +21,10 @@ export interface TransferResult {
21
21
  metadata: Record<string, unknown>;
22
22
  }
23
23
  export interface DisperseParams {
24
- /** 主钱包私钥(发起者,支付 gas) */
24
+ /** 资金来源钱包私钥(Token/OKB 从这个钱包转出) */
25
25
  mainPrivateKey: string;
26
+ /** Payer 钱包私钥(可选,用于支付 gas;不传则使用 mainPrivateKey) */
27
+ payerPrivateKey?: string;
26
28
  /** 接收者地址列表 */
27
29
  recipients: string[];
28
30
  /** 每个接收者的金额 */
@@ -101,16 +101,23 @@ async function quoteTokenToOkb(tokenAddress, tokenAmount, rpcUrl) {
101
101
  * });
102
102
  */
103
103
  export async function disperse(params) {
104
- const { mainPrivateKey, recipients, amounts, tokenAddress, tokenDecimals = 18, hopCount = 0, userType, config, } = params;
104
+ const { mainPrivateKey, payerPrivateKey, recipients, amounts, tokenAddress, tokenDecimals = 18, hopCount = 0, userType, config, } = params;
105
105
  if (recipients.length !== amounts.length) {
106
106
  throw new Error('recipients 和 amounts 长度必须相同');
107
107
  }
108
108
  const provider = getCachedProvider(config?.rpcUrl);
109
109
  const delegateAddress = (config?.delegateAddress && config.delegateAddress.trim()) || UNIFIED_DELEGATE_ADDRESS;
110
110
  const delegateInterface = new ethers.Interface(UNIFIED_DELEGATE_ABI);
111
+ // ✅ mainWallet: 资金来源(Token/OKB 从这里转出)
111
112
  const mainWallet = createWallet(mainPrivateKey, provider);
112
113
  const isNative = !tokenAddress;
113
114
  const profitRecipient = getProfitRecipient(config);
115
+ // ✅ 支持单独的 Payer 钱包(用于支付 gas)
116
+ const hasSeparatePayer = payerPrivateKey && payerPrivateKey !== mainPrivateKey;
117
+ const payerWallet = hasSeparatePayer ? createWallet(payerPrivateKey, provider) : null;
118
+ const txSender = payerWallet ?? mainWallet; // 交易发起者
119
+ console.log(`[disperse] 资金钱包: ${mainWallet.address}`);
120
+ console.log(`[disperse] Gas Payer: ${txSender.address}${hasSeparatePayer ? ' (独立 Payer)' : ''}`);
114
121
  // ✅ 为每个接收者生成独立的中间钱包链
115
122
  // 每个接收者有自己的 hopCount 个中间钱包
116
123
  const allHopWalletInfos = [];
@@ -145,23 +152,28 @@ export async function disperse(params) {
145
152
  // ========================================
146
153
  // 并行获取数据
147
154
  // ========================================
148
- const [mainNonce, feeData] = await Promise.all([
149
- provider.getTransactionCount(mainWallet.address, 'pending'),
155
+ const addressesToGetNonces = payerWallet
156
+ ? [payerWallet.address, mainWallet.address]
157
+ : [mainWallet.address];
158
+ const [allNoncesResult, feeData] = await Promise.all([
159
+ batchGetNonces(addressesToGetNonces, provider),
150
160
  provider.getFeeData(),
151
161
  ]);
162
+ const payerNonce = payerWallet ? allNoncesResult[0] : allNoncesResult[0];
163
+ const mainNonce = payerWallet ? allNoncesResult[1] : allNoncesResult[0];
152
164
  // ========================================
153
165
  // 同步签署授权(已有 nonce)
154
166
  // ========================================
155
- // 主钱包 + 所有中间钱包
156
- const allWallets = [mainWallet];
157
- const allNonces = [mainNonce];
167
+ // 如果有 Payer,需要为 Payer 和 mainWallet 都生成授权
168
+ const allWallets = payerWallet ? [payerWallet, mainWallet] : [mainWallet];
169
+ const allNonces = payerWallet ? [payerNonce, mainNonce] : [mainNonce];
158
170
  for (const hopWallets of allHopWallets) {
159
171
  for (const hopWallet of hopWallets) {
160
172
  allWallets.push(hopWallet);
161
173
  allNonces.push(0);
162
174
  }
163
175
  }
164
- const authorizations = signAuthorizationsWithNonces(allWallets, allNonces, delegateAddress, 0 // mainWallet 是交易发起者
176
+ const authorizations = signAuthorizationsWithNonces(allWallets, allNonces, delegateAddress, 0 // txSender(Payer 或 mainWallet)是交易发起者
165
177
  );
166
178
  // ========================================
167
179
  // 同步构建调用
@@ -176,12 +188,24 @@ export async function disperse(params) {
176
188
  // 直接分发:主钱包 → 接收者
177
189
  // ========================================
178
190
  if (isNative) {
179
- calls.push({
180
- target: mainWallet.address,
181
- allowFailure: false,
182
- value: amtWei,
183
- callData: delegateInterface.encodeFunctionData('transferTo', [recipient]),
184
- });
191
+ // ✅ Payer 模式:使用 transferAmount(从 mainWallet 余额转出)
192
+ // ✅ 非 Payer 模式:使用 transferTo(从 msg.value 转出)
193
+ if (payerWallet) {
194
+ calls.push({
195
+ target: mainWallet.address,
196
+ allowFailure: false,
197
+ value: 0n,
198
+ callData: delegateInterface.encodeFunctionData('transferAmount', [recipient, amtWei]),
199
+ });
200
+ }
201
+ else {
202
+ calls.push({
203
+ target: mainWallet.address,
204
+ allowFailure: false,
205
+ value: amtWei,
206
+ callData: delegateInterface.encodeFunctionData('transferTo', [recipient]),
207
+ });
208
+ }
185
209
  }
186
210
  else {
187
211
  calls.push({
@@ -204,13 +228,24 @@ export async function disperse(params) {
204
228
  const lastHop = hopWallets[hopWallets.length - 1];
205
229
  if (isNative) {
206
230
  // 主钱包 → 第一个中间钱包
207
- calls.push({
208
- target: mainWallet.address,
209
- allowFailure: false,
210
- value: amtWei,
211
- callData: delegateInterface.encodeFunctionData('transferTo', [firstHop.address]),
212
- });
213
- // 中间钱包之间转账
231
+ // ✅ Payer 模式:使用 transferAmount(从 mainWallet 余额转出)
232
+ if (payerWallet) {
233
+ calls.push({
234
+ target: mainWallet.address,
235
+ allowFailure: false,
236
+ value: 0n,
237
+ callData: delegateInterface.encodeFunctionData('transferAmount', [firstHop.address, amtWei]),
238
+ });
239
+ }
240
+ else {
241
+ calls.push({
242
+ target: mainWallet.address,
243
+ allowFailure: false,
244
+ value: amtWei,
245
+ callData: delegateInterface.encodeFunctionData('transferTo', [firstHop.address]),
246
+ });
247
+ }
248
+ // 中间钱包之间转账(使用全部余额)
214
249
  for (let j = 0; j < hopWallets.length - 1; j++) {
215
250
  calls.push({
216
251
  target: hopWallets[j].address,
@@ -219,7 +254,7 @@ export async function disperse(params) {
219
254
  callData: delegateInterface.encodeFunctionData('transferTo', [hopWallets[j + 1].address]),
220
255
  });
221
256
  }
222
- // 最后一个中间钱包 → 接收者
257
+ // 最后一个中间钱包 → 接收者(使用全部余额)
223
258
  calls.push({
224
259
  target: lastHop.address,
225
260
  allowFailure: false,
@@ -280,15 +315,20 @@ export async function disperse(params) {
280
315
  // ========================================
281
316
  // 构建交易
282
317
  // ========================================
283
- const totalValue = isNative ? totalAmount : 0n;
284
- const signedTransaction = buildEIP7702TransactionSync(mainWallet, authorizations, calls, totalValue, mainNonce, feeData, config);
318
+ // Payer 模式下 value 0(资金从 mainWallet 转出,不是 Payer)
319
+ // Payer 时,原生币分发需要传递 value(资金从 txSender 转出)
320
+ const totalValue = (isNative && !payerWallet) ? totalAmount : 0n;
321
+ const signedTransaction = buildEIP7702TransactionSync(txSender, // ✅ 使用 txSender(Payer 或 mainWallet)发起交易
322
+ authorizations, calls, totalValue, payerNonce, // ✅ 使用 txSender 的 nonce
323
+ feeData, config);
285
324
  // ✅ 返回所有中间钱包(扁平化)
286
325
  const flatHopWallets = allHopWalletInfos.flat();
287
326
  return {
288
327
  signedTransaction,
289
328
  hopWallets: flatHopWallets.length > 0 ? flatHopWallets : undefined,
290
329
  metadata: {
291
- senderAddress: mainWallet.address,
330
+ senderAddress: mainWallet.address, // 资金来源
331
+ payerAddress: txSender.address, // Gas 支付者
292
332
  recipientCount: recipients.length,
293
333
  totalAmount: isNative ? ethers.formatEther(totalAmount) : ethers.formatUnits(totalAmount, tokenDecimals),
294
334
  profitAmount: ethers.formatEther(profitAmountOkb), // ✅ 以 OKB 计
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.7.60",
3
+ "version": "1.7.62",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",