four-flap-meme-sdk 1.7.61 → 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,9 +315,14 @@ 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
|
// ========================================
|
|
@@ -326,12 +331,21 @@ export async function bundleSwap(params) {
|
|
|
326
331
|
batchGetNonces([sellerWallet.address, buyerWallet.address], provider),
|
|
327
332
|
// 获取 gas 费用数据
|
|
328
333
|
provider.getFeeData(),
|
|
329
|
-
// 获取买家的 OKB
|
|
334
|
+
// 获取买家的 OKB 余额(验证是否足够)
|
|
330
335
|
provider.getBalance(buyerWallet.address),
|
|
331
336
|
]);
|
|
332
337
|
const sellerNonce = nonces[0];
|
|
333
338
|
const buyerNonce = nonces[1];
|
|
334
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
|
+
}
|
|
335
349
|
// ========================================
|
|
336
350
|
// 同步签署授权(只需要卖家和买家,不需要 hop wallets)
|
|
337
351
|
// ========================================
|
|
@@ -342,22 +356,11 @@ export async function bundleSwap(params) {
|
|
|
342
356
|
authorizations.push(signAuthorization(buyerWallet, delegateAddress, BigInt(buyerNonce)));
|
|
343
357
|
// 等待所有授权签名完成
|
|
344
358
|
const resolvedAuthorizations = await Promise.all(authorizations);
|
|
345
|
-
// ========================================
|
|
346
|
-
// 同步构建调用
|
|
347
|
-
// ========================================
|
|
348
|
-
// ✅ 计算买入金额(普通模式:买家用自己的 OKB)
|
|
349
|
-
// FLAP 模式需要使用买家的实际余额,扣除利润后的金额
|
|
350
|
-
const buyerAvailable = buyerBalance > profitAmount ? buyerBalance - profitAmount : 0n;
|
|
351
|
-
console.log(`[Bundle Swap] 买家可用金额(扣除利润后): ${ethers.formatEther(buyerAvailable)} OKB`);
|
|
352
|
-
// ✅ FLAP 模式需要买家有足够的 OKB
|
|
353
|
-
if (tradeType === 'FLAP' && buyerAvailable <= 0n) {
|
|
354
|
-
throw new Error('FLAP 内盘模式需要买家钱包有足够的 OKB');
|
|
355
|
-
}
|
|
356
359
|
const calls = [];
|
|
357
360
|
// ========================================
|
|
358
361
|
// ✅ 普通换手模式逻辑
|
|
359
362
|
// 1. 卖家卖出代币 → OKB 留在卖家账户
|
|
360
|
-
// 2. 买家用自己预存的 OKB
|
|
363
|
+
// 2. 买家用自己预存的 OKB 买入代币(金额 = 卖家卖出所得,不是全部余额)
|
|
361
364
|
// 3. 利润从买家的 OKB 中扣除
|
|
362
365
|
// ========================================
|
|
363
366
|
// 1. 卖家卖出(OKB 留在卖家账户,不转给买家)
|
|
@@ -373,8 +376,10 @@ export async function bundleSwap(params) {
|
|
|
373
376
|
});
|
|
374
377
|
}
|
|
375
378
|
// 3. 买家用自己预存的 OKB 买入
|
|
376
|
-
// ✅
|
|
377
|
-
|
|
379
|
+
// ✅ 关键修复:买入金额 = 卖家卖出所得(扣除利润),而不是买家全部余额
|
|
380
|
+
// FLAP 模式必须指定精确金额,V2/V3 使用 0n(合约自动使用全部余额)
|
|
381
|
+
const buyAmountForCall = tradeType === 'FLAP' ? buyAmountAfterProfit : 0n;
|
|
382
|
+
console.log(`[Bundle Swap] 实际买入调用金额: ${ethers.formatEther(buyAmountForCall)} OKB`);
|
|
378
383
|
const buyCall = buildBuyCallInternal(buyerWallet, buyAmountForCall, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
379
384
|
calls.push(buyCall);
|
|
380
385
|
// ========================================
|
|
@@ -436,11 +441,16 @@ export async function bundleBatchSwap(params) {
|
|
|
436
441
|
? calculateProfitAmountByUserType(estimatedOkbOut, userType, true) // isDoubleMode = true
|
|
437
442
|
: 0n;
|
|
438
443
|
const profitRecipient = getProfitRecipient(config);
|
|
444
|
+
// ✅ 计算总买入金额:基于卖家卖出得到的 OKB(不是买家全部余额)
|
|
445
|
+
const totalBuyAmountAfterProfit = estimatedOkbOut > profitAmount
|
|
446
|
+
? estimatedOkbOut - profitAmount
|
|
447
|
+
: 0n;
|
|
439
448
|
console.log(`[Bundle Batch Swap] 卖出数量: ${sellAmount} Token`);
|
|
440
449
|
console.log(`[Bundle Batch Swap] 预估获得: ${ethers.formatEther(estimatedOkbOut)} OKB`);
|
|
441
450
|
console.log(`[Bundle Batch Swap] 利润: ${ethers.formatEther(profitAmount)} OKB (userType=${userType})`);
|
|
451
|
+
console.log(`[Bundle Batch Swap] 买家总应买入: ${ethers.formatEther(totalBuyAmountAfterProfit)} OKB(基于卖出所得)`);
|
|
442
452
|
// ✅ FLAP 模式需要有效的报价(不能使用 0n 作为买入金额)
|
|
443
|
-
if (tradeType === 'FLAP' &&
|
|
453
|
+
if (tradeType === 'FLAP' && totalBuyAmountAfterProfit <= 0n) {
|
|
444
454
|
throw new Error('FLAP 内盘模式需要有效的报价,请检查代币地址或稍后重试');
|
|
445
455
|
}
|
|
446
456
|
// ========================================
|
|
@@ -450,12 +460,19 @@ export async function bundleBatchSwap(params) {
|
|
|
450
460
|
const [nonces, feeData, ...buyerBalances] = await Promise.all([
|
|
451
461
|
batchGetNonces(addressesToQuery, provider),
|
|
452
462
|
provider.getFeeData(),
|
|
453
|
-
// 获取所有买家的 OKB
|
|
463
|
+
// 获取所有买家的 OKB 余额(验证是否足够)
|
|
454
464
|
...buyerWallets.map(w => provider.getBalance(w.address)),
|
|
455
465
|
]);
|
|
456
466
|
const sellerNonce = nonces[0];
|
|
457
467
|
const buyerNonces = nonces.slice(1);
|
|
458
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
|
+
}
|
|
459
476
|
// ========================================
|
|
460
477
|
// 同步构建调用
|
|
461
478
|
// ========================================
|
|
@@ -480,17 +497,37 @@ export async function bundleBatchSwap(params) {
|
|
|
480
497
|
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
481
498
|
});
|
|
482
499
|
}
|
|
483
|
-
// ✅ 3.
|
|
484
|
-
//
|
|
500
|
+
// ✅ 3. 计算每个买家的买入金额
|
|
501
|
+
// 关键修复:买入金额 = 卖家卖出所得(按比例分配),而不是买家全部余额
|
|
485
502
|
let buyAmountsPerBuyer;
|
|
486
503
|
if (tradeType === 'FLAP') {
|
|
487
|
-
// FLAP
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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)));
|
|
494
531
|
}
|
|
495
532
|
else {
|
|
496
533
|
// V2/V3 模式:使用 0n,合约自动使用全部余额
|
|
@@ -21,8 +21,10 @@ export interface TransferResult {
|
|
|
21
21
|
metadata: Record<string, unknown>;
|
|
22
22
|
}
|
|
23
23
|
export interface DisperseParams {
|
|
24
|
-
/**
|
|
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
|
|
149
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
284
|
-
|
|
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 计
|