four-flap-meme-sdk 1.3.70 → 1.3.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contracts/tm-bundle-merkle/swap.d.ts +34 -0
- package/dist/contracts/tm-bundle-merkle/swap.js +198 -0
- package/dist/flap/portal-bundle-merkle/swap.d.ts +36 -0
- package/dist/flap/portal-bundle-merkle/swap.js +194 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/pancake/bundle-swap.d.ts +1 -0
- package/dist/pancake/bundle-swap.js +57 -64
- package/package.json +2 -2
|
@@ -59,3 +59,37 @@ export type FourSwapResult = {
|
|
|
59
59
|
* Four内盘捆绑换手
|
|
60
60
|
*/
|
|
61
61
|
export declare function fourBundleSwapMerkle(params: FourBundleSwapSignParams): Promise<FourSwapResult>;
|
|
62
|
+
/**
|
|
63
|
+
* Four 批量换手参数(一卖多买)
|
|
64
|
+
*/
|
|
65
|
+
export interface FourBatchSwapSignParams {
|
|
66
|
+
sellerPrivateKey: string;
|
|
67
|
+
sellAmount?: string;
|
|
68
|
+
sellPercentage?: number;
|
|
69
|
+
buyerPrivateKeys: string[];
|
|
70
|
+
buyerRatios?: number[];
|
|
71
|
+
tokenAddress: string;
|
|
72
|
+
slippageTolerance?: number;
|
|
73
|
+
config: FourSwapSignConfig;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Four 批量换手结果
|
|
77
|
+
*/
|
|
78
|
+
export interface FourBatchSwapResult {
|
|
79
|
+
signedTransactions: string[];
|
|
80
|
+
metadata?: {
|
|
81
|
+
sellerAddress: string;
|
|
82
|
+
buyerAddresses: string[];
|
|
83
|
+
sellAmount: string;
|
|
84
|
+
buyAmounts: string[];
|
|
85
|
+
hasApproval?: boolean;
|
|
86
|
+
profitAmount?: string;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Four 内盘批量换手(一卖多买)
|
|
91
|
+
*
|
|
92
|
+
* 功能:主钱包一次卖出 → 多个子钱包同时买入 → 在同一个区块中完成
|
|
93
|
+
* 限制:最多 24 个买方(服务器限制 25 笔交易,包含 1 笔利润交易)
|
|
94
|
+
*/
|
|
95
|
+
export declare function fourBatchSwapMerkle(params: FourBatchSwapSignParams): Promise<FourBatchSwapResult>;
|
|
@@ -178,3 +178,201 @@ export async function fourBundleSwapMerkle(params) {
|
|
|
178
178
|
}
|
|
179
179
|
};
|
|
180
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Four 内盘批量换手(一卖多买)
|
|
183
|
+
*
|
|
184
|
+
* 功能:主钱包一次卖出 → 多个子钱包同时买入 → 在同一个区块中完成
|
|
185
|
+
* 限制:最多 24 个买方(服务器限制 25 笔交易,包含 1 笔利润交易)
|
|
186
|
+
*/
|
|
187
|
+
export async function fourBatchSwapMerkle(params) {
|
|
188
|
+
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, slippageTolerance = 0.5, config } = params;
|
|
189
|
+
// ✅ 校验买方数量(最多 24 个)
|
|
190
|
+
const MAX_BUYERS = 24;
|
|
191
|
+
if (buyerPrivateKeys.length === 0) {
|
|
192
|
+
throw new Error('至少需要一个买方钱包');
|
|
193
|
+
}
|
|
194
|
+
if (buyerPrivateKeys.length > MAX_BUYERS) {
|
|
195
|
+
throw new Error(`买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
|
|
196
|
+
}
|
|
197
|
+
const chainIdNum = config.chainId ?? 56;
|
|
198
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
|
|
199
|
+
chainId: chainIdNum,
|
|
200
|
+
name: 'BSC'
|
|
201
|
+
});
|
|
202
|
+
const seller = new Wallet(sellerPrivateKey, provider);
|
|
203
|
+
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, provider));
|
|
204
|
+
const nonceManager = new NonceManager(provider);
|
|
205
|
+
console.log('🔍 Four BatchSwap - 买方数量:', buyerPrivateKeys.length);
|
|
206
|
+
// 创建适配的配置对象
|
|
207
|
+
const bundleConfig = {
|
|
208
|
+
minGasPriceGwei: config.minGasPriceGwei,
|
|
209
|
+
maxGasPriceGwei: config.maxGasPriceGwei,
|
|
210
|
+
gasLimit: typeof config.gasLimit === 'bigint' ? Number(config.gasLimit) : config.gasLimit,
|
|
211
|
+
gasLimitMultiplier: config.gasLimitMultiplier,
|
|
212
|
+
txType: config.txType,
|
|
213
|
+
chainId: config.chainId
|
|
214
|
+
};
|
|
215
|
+
const finalGasLimit = getGasLimit(bundleConfig);
|
|
216
|
+
const txType = getTxType(bundleConfig);
|
|
217
|
+
// ✅ 并行获取:卖出数量、gasPrice
|
|
218
|
+
const [sellAmountResult, gasPrice] = await Promise.all([
|
|
219
|
+
calculateSellAmount(provider, tokenAddress, seller.address, sellAmount, sellPercentage),
|
|
220
|
+
getOptimizedGasPrice(provider, getGasPriceConfig(bundleConfig))
|
|
221
|
+
]);
|
|
222
|
+
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
223
|
+
// ✅ 并行获取:sellQuote、allowance、所有买方余额
|
|
224
|
+
const helper3 = new Contract(ADDRESSES.BSC.TokenManagerHelper3, HELPER3_ABI, provider);
|
|
225
|
+
const erc20Contract = new Contract(tokenAddress, ['function allowance(address,address) view returns (uint256)'], provider);
|
|
226
|
+
const [sellQuote, currentAllowance, ...buyerBalances] = await Promise.all([
|
|
227
|
+
helper3.trySell(tokenAddress, sellAmountWei),
|
|
228
|
+
erc20Contract.allowance(seller.address, TM_ADDRESS),
|
|
229
|
+
...buyers.map(buyer => provider.getBalance(buyer.address))
|
|
230
|
+
]);
|
|
231
|
+
const totalBuyerFunds = sellQuote.funds;
|
|
232
|
+
// ✅ 计算每个买方的买入金额(按比例分配)
|
|
233
|
+
let buyAmountsWei;
|
|
234
|
+
if (params.buyerRatios && params.buyerRatios.length === buyers.length) {
|
|
235
|
+
// 按比例分配
|
|
236
|
+
console.log('📊 使用比例分配,卖出所得:', ethers.formatEther(totalBuyerFunds), 'BNB');
|
|
237
|
+
buyAmountsWei = params.buyerRatios.map((ratio, index) => {
|
|
238
|
+
const amount = (totalBuyerFunds * BigInt(Math.round(ratio * 10000))) / 10000n;
|
|
239
|
+
console.log(` 买方 ${index + 1}: ${(ratio * 100).toFixed(1)}% = ${ethers.formatEther(amount)} BNB`);
|
|
240
|
+
return amount;
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
// 平均分配
|
|
245
|
+
const amountPerBuyer = totalBuyerFunds / BigInt(buyers.length);
|
|
246
|
+
buyAmountsWei = buyers.map(() => amountPerBuyer);
|
|
247
|
+
}
|
|
248
|
+
// ✅ 验证所有买方余额
|
|
249
|
+
const reserveGas = ethers.parseEther((config.reserveGasBNB || 0.0005).toString());
|
|
250
|
+
buyers.forEach((buyer, i) => {
|
|
251
|
+
const required = buyAmountsWei[i] + reserveGas;
|
|
252
|
+
if (buyerBalances[i] < required) {
|
|
253
|
+
throw new Error(`买方 ${i + 1} 余额不足: 需要 ${ethers.formatEther(required)} BNB, 实际 ${ethers.formatEther(buyerBalances[i])} BNB`);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
// 检查是否需要授权
|
|
257
|
+
const APPROVAL_THRESHOLD = ethers.parseUnits('1000000000', decimals);
|
|
258
|
+
const needApproval = currentAllowance < APPROVAL_THRESHOLD;
|
|
259
|
+
// 利润配置
|
|
260
|
+
const profitRateBps = PROFIT_CONFIG.RATE_BPS;
|
|
261
|
+
const profitAmount = totalBuyerFunds > 0n
|
|
262
|
+
? (totalBuyerFunds * BigInt(profitRateBps)) / 10000n
|
|
263
|
+
: 0n;
|
|
264
|
+
// ✅ 并行构建所有交易
|
|
265
|
+
const tmSeller = new Contract(TM_ADDRESS, TM_ABI, seller);
|
|
266
|
+
const [sellUnsigned, ...buyUnsignedList] = await Promise.all([
|
|
267
|
+
// 卖出交易
|
|
268
|
+
tmSeller.sellToken.populateTransaction(0n, tokenAddress, sellAmountWei, 0n),
|
|
269
|
+
// 所有买入交易
|
|
270
|
+
...buyers.map((buyer, i) => {
|
|
271
|
+
const tmBuyer = new Contract(TM_ADDRESS, TM_ABI, buyer);
|
|
272
|
+
const buyAmount = buyAmountsWei[i];
|
|
273
|
+
return tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, buyAmount, 0n, { value: buyAmount });
|
|
274
|
+
})
|
|
275
|
+
]);
|
|
276
|
+
// ✅ 计算 nonce
|
|
277
|
+
// seller 需要的 nonce 数量
|
|
278
|
+
let sellerNonceCount = 1; // 卖出交易
|
|
279
|
+
if (needApproval)
|
|
280
|
+
sellerNonceCount++; // 授权交易
|
|
281
|
+
sellerNonceCount++; // 利润交易
|
|
282
|
+
// 并行获取所有初始 nonce
|
|
283
|
+
const allWallets = [seller, ...buyers];
|
|
284
|
+
const initialNonces = await nonceManager.getNextNoncesForWallets(allWallets);
|
|
285
|
+
// 分配 seller nonces
|
|
286
|
+
const sellerNonces = Array.from({ length: sellerNonceCount }, (_, i) => initialNonces[0] + i);
|
|
287
|
+
let idx = 0;
|
|
288
|
+
let approvalNonce;
|
|
289
|
+
if (needApproval)
|
|
290
|
+
approvalNonce = sellerNonces[idx++];
|
|
291
|
+
const sellerNonce = sellerNonces[idx++];
|
|
292
|
+
const profitNonce = sellerNonces[idx];
|
|
293
|
+
// buyer nonces
|
|
294
|
+
const buyerNonces = initialNonces.slice(1);
|
|
295
|
+
// ✅ 并行签名所有交易
|
|
296
|
+
const signPromises = [];
|
|
297
|
+
// 授权交易
|
|
298
|
+
if (needApproval && approvalNonce !== undefined) {
|
|
299
|
+
const approveInterface = new ethers.Interface(['function approve(address,uint256) returns (bool)']);
|
|
300
|
+
const approveData = approveInterface.encodeFunctionData('approve', [TM_ADDRESS, ethers.MaxUint256]);
|
|
301
|
+
signPromises.push(seller.signTransaction({
|
|
302
|
+
to: tokenAddress,
|
|
303
|
+
data: approveData,
|
|
304
|
+
value: 0n,
|
|
305
|
+
nonce: approvalNonce,
|
|
306
|
+
gasLimit: 80000n,
|
|
307
|
+
gasPrice,
|
|
308
|
+
chainId: chainIdNum,
|
|
309
|
+
type: txType
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
// 卖出交易
|
|
313
|
+
signPromises.push(seller.signTransaction({
|
|
314
|
+
...sellUnsigned,
|
|
315
|
+
from: seller.address,
|
|
316
|
+
nonce: sellerNonce,
|
|
317
|
+
gasLimit: finalGasLimit,
|
|
318
|
+
gasPrice,
|
|
319
|
+
chainId: chainIdNum,
|
|
320
|
+
type: txType
|
|
321
|
+
}));
|
|
322
|
+
// 所有买入交易
|
|
323
|
+
buyers.forEach((buyer, i) => {
|
|
324
|
+
signPromises.push(buyer.signTransaction({
|
|
325
|
+
...buyUnsignedList[i],
|
|
326
|
+
from: buyer.address,
|
|
327
|
+
nonce: buyerNonces[i],
|
|
328
|
+
gasLimit: finalGasLimit,
|
|
329
|
+
gasPrice,
|
|
330
|
+
chainId: chainIdNum,
|
|
331
|
+
type: txType,
|
|
332
|
+
value: buyAmountsWei[i]
|
|
333
|
+
}));
|
|
334
|
+
});
|
|
335
|
+
// 利润交易
|
|
336
|
+
if (profitAmount > 0n) {
|
|
337
|
+
signPromises.push(seller.signTransaction({
|
|
338
|
+
to: PROFIT_CONFIG.RECIPIENT,
|
|
339
|
+
value: profitAmount,
|
|
340
|
+
nonce: profitNonce,
|
|
341
|
+
gasPrice,
|
|
342
|
+
gasLimit: 21000n,
|
|
343
|
+
chainId: chainIdNum,
|
|
344
|
+
type: txType
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
const signedTxs = await Promise.all(signPromises);
|
|
348
|
+
// 解析签名结果
|
|
349
|
+
let signIdx = 0;
|
|
350
|
+
let approvalTx = null;
|
|
351
|
+
if (needApproval)
|
|
352
|
+
approvalTx = signedTxs[signIdx++];
|
|
353
|
+
const signedSell = signedTxs[signIdx++];
|
|
354
|
+
const signedBuys = signedTxs.slice(signIdx, signIdx + buyers.length);
|
|
355
|
+
signIdx += buyers.length;
|
|
356
|
+
const profitTx = profitAmount > 0n ? signedTxs[signIdx] : null;
|
|
357
|
+
nonceManager.clearTemp();
|
|
358
|
+
// ✅ 按顺序组装交易数组
|
|
359
|
+
const signedTransactions = [];
|
|
360
|
+
if (approvalTx)
|
|
361
|
+
signedTransactions.push(approvalTx);
|
|
362
|
+
signedTransactions.push(signedSell);
|
|
363
|
+
signedTransactions.push(...signedBuys);
|
|
364
|
+
if (profitTx)
|
|
365
|
+
signedTransactions.push(profitTx);
|
|
366
|
+
console.log(`✅ Four 批量换手签名完成: ${signedTransactions.length} 笔交易`);
|
|
367
|
+
return {
|
|
368
|
+
signedTransactions,
|
|
369
|
+
metadata: {
|
|
370
|
+
sellerAddress: seller.address,
|
|
371
|
+
buyerAddresses: buyers.map(b => b.address),
|
|
372
|
+
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
373
|
+
buyAmounts: buyAmountsWei.map(amt => ethers.formatEther(amt)),
|
|
374
|
+
hasApproval: !!approvalTx,
|
|
375
|
+
profitAmount: ethers.formatEther(profitAmount)
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
}
|
|
@@ -66,3 +66,39 @@ export type FlapSwapResult = {
|
|
|
66
66
|
* ✅ 支持 quoteToken:传入 USDT 等地址时,卖出得到该代币,买入使用该代币
|
|
67
67
|
*/
|
|
68
68
|
export declare function flapBundleSwapMerkle(params: FlapBundleSwapSignParams): Promise<FlapSwapResult>;
|
|
69
|
+
/**
|
|
70
|
+
* Flap 批量换手参数(一卖多买)
|
|
71
|
+
*/
|
|
72
|
+
export interface FlapBatchSwapSignParams {
|
|
73
|
+
chain: FlapChain;
|
|
74
|
+
sellerPrivateKey: string;
|
|
75
|
+
sellAmount?: string;
|
|
76
|
+
sellPercentage?: number;
|
|
77
|
+
buyerPrivateKeys: string[];
|
|
78
|
+
buyerRatios?: number[];
|
|
79
|
+
tokenAddress: string;
|
|
80
|
+
config: FlapSwapSignConfig;
|
|
81
|
+
quoteToken?: string;
|
|
82
|
+
quoteTokenDecimals?: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Flap 批量换手结果
|
|
86
|
+
*/
|
|
87
|
+
export interface FlapBatchSwapResult {
|
|
88
|
+
signedTransactions: string[];
|
|
89
|
+
metadata?: {
|
|
90
|
+
sellerAddress: string;
|
|
91
|
+
buyerAddresses: string[];
|
|
92
|
+
sellAmount: string;
|
|
93
|
+
buyAmounts: string[];
|
|
94
|
+
hasApproval?: boolean;
|
|
95
|
+
profitAmount?: string;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Flap 内盘批量换手(一卖多买)
|
|
100
|
+
*
|
|
101
|
+
* 功能:主钱包一次卖出 → 多个子钱包同时买入 → 在同一个区块中完成
|
|
102
|
+
* 限制:最多 24 个买方(服务器限制 25 笔交易,包含 1 笔利润交易)
|
|
103
|
+
*/
|
|
104
|
+
export declare function flapBatchSwapMerkle(params: FlapBatchSwapSignParams): Promise<FlapBatchSwapResult>;
|
|
@@ -454,3 +454,197 @@ function calculateProfitAmount(quotedNative) {
|
|
|
454
454
|
function countTruthy(values) {
|
|
455
455
|
return values.filter(Boolean).length;
|
|
456
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* Flap 内盘批量换手(一卖多买)
|
|
459
|
+
*
|
|
460
|
+
* 功能:主钱包一次卖出 → 多个子钱包同时买入 → 在同一个区块中完成
|
|
461
|
+
* 限制:最多 24 个买方(服务器限制 25 笔交易,包含 1 笔利润交易)
|
|
462
|
+
*/
|
|
463
|
+
export async function flapBatchSwapMerkle(params) {
|
|
464
|
+
const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, config, quoteToken, quoteTokenDecimals = 18 } = params;
|
|
465
|
+
// ✅ 校验买方数量(最多 24 个)
|
|
466
|
+
const MAX_BUYERS = 24;
|
|
467
|
+
if (buyerPrivateKeys.length === 0) {
|
|
468
|
+
throw new Error('至少需要一个买方钱包');
|
|
469
|
+
}
|
|
470
|
+
if (buyerPrivateKeys.length > MAX_BUYERS) {
|
|
471
|
+
throw new Error(`买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
|
|
472
|
+
}
|
|
473
|
+
const chainContext = createChainContext(chain, config);
|
|
474
|
+
const seller = new Wallet(sellerPrivateKey, chainContext.provider);
|
|
475
|
+
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
|
|
476
|
+
const nonceManager = new NonceManager(chainContext.provider);
|
|
477
|
+
const finalGasLimit = getGasLimit(config);
|
|
478
|
+
const txType = getTxType(config);
|
|
479
|
+
// ✅ 判断是否使用原生代币
|
|
480
|
+
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
481
|
+
const outputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
|
|
482
|
+
console.log('🔍 Flap BatchSwap - 买方数量:', buyerPrivateKeys.length);
|
|
483
|
+
console.log('🔍 Flap BatchSwap - useNativeToken:', useNativeToken);
|
|
484
|
+
// ✅ 并行获取:卖出数量、gasPrice
|
|
485
|
+
const [sellAmountResult, gasPrice] = await Promise.all([
|
|
486
|
+
calculateSellAmount(chainContext.provider, tokenAddress, seller.address, sellAmount, sellPercentage),
|
|
487
|
+
getOptimizedGasPrice(chainContext.provider, getGasPriceConfig(config))
|
|
488
|
+
]);
|
|
489
|
+
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
490
|
+
const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
|
|
491
|
+
// ✅ 并行获取:授权、报价
|
|
492
|
+
const [approvalTx, quote] = await Promise.all([
|
|
493
|
+
config.skipApprovalCheck
|
|
494
|
+
? Promise.resolve(null)
|
|
495
|
+
: buildApprovalTransaction({
|
|
496
|
+
tokenAddress,
|
|
497
|
+
seller,
|
|
498
|
+
provider: chainContext.provider,
|
|
499
|
+
decimals,
|
|
500
|
+
portalAddress: chainContext.portalAddress,
|
|
501
|
+
chainId: chainContext.chainId,
|
|
502
|
+
config
|
|
503
|
+
}),
|
|
504
|
+
quoteSellOutput({
|
|
505
|
+
portalAddress: chainContext.portalAddress,
|
|
506
|
+
tokenAddress,
|
|
507
|
+
sellAmountWei,
|
|
508
|
+
provider: chainContext.provider,
|
|
509
|
+
slippageBps: config.slippageBps,
|
|
510
|
+
skipQuoteOnError: config.skipQuoteOnError,
|
|
511
|
+
outputToken
|
|
512
|
+
})
|
|
513
|
+
]);
|
|
514
|
+
// ✅ 计算每个买方的买入金额(按比例分配)
|
|
515
|
+
const safeSlippage = Math.max(0, Math.min(5000, config.slippageBps ?? 100));
|
|
516
|
+
const increase = BigInt(10000 + safeSlippage);
|
|
517
|
+
const totalBuyAmount = (quote.quotedNative * increase) / 10000n;
|
|
518
|
+
let buyAmountsWei;
|
|
519
|
+
if (params.buyerRatios && params.buyerRatios.length === buyers.length) {
|
|
520
|
+
// 按比例分配
|
|
521
|
+
console.log('📊 使用比例分配,卖出所得:', ethers.formatEther(totalBuyAmount));
|
|
522
|
+
buyAmountsWei = params.buyerRatios.map((ratio, index) => {
|
|
523
|
+
const amount = (totalBuyAmount * BigInt(Math.round(ratio * 10000))) / 10000n;
|
|
524
|
+
console.log(` 买方 ${index + 1}: ${(ratio * 100).toFixed(1)}% = ${ethers.formatEther(amount)}`);
|
|
525
|
+
return amount;
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
// 平均分配
|
|
530
|
+
const amountPerBuyer = totalBuyAmount / BigInt(buyers.length);
|
|
531
|
+
buyAmountsWei = buyers.map(() => amountPerBuyer);
|
|
532
|
+
}
|
|
533
|
+
// ✅ 并行验证所有买方余额
|
|
534
|
+
const reserveGas = ethers.parseEther((config.reserveGasETH || 0.0005).toString());
|
|
535
|
+
const erc20Contract = useNativeToken ? null : new Contract(quoteToken, ERC20_BALANCE_OF_ABI, chainContext.provider);
|
|
536
|
+
await Promise.all(buyers.map(async (buyer, i) => {
|
|
537
|
+
const buyAmount = buyAmountsWei[i];
|
|
538
|
+
if (useNativeToken) {
|
|
539
|
+
const buyerBalance = await buyer.provider.getBalance(buyer.address);
|
|
540
|
+
const required = buyAmount + reserveGas;
|
|
541
|
+
if (buyerBalance < required) {
|
|
542
|
+
throw new Error(`买方 ${i + 1} 余额不足: 需要 ${ethers.formatEther(required)} ${chainContext.nativeToken}, 实际 ${ethers.formatEther(buyerBalance)}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
const buyerBalance = await erc20Contract.balanceOf(buyer.address);
|
|
547
|
+
if (buyerBalance < buyAmount) {
|
|
548
|
+
throw new Error(`买方 ${i + 1} 代币余额不足: 需要 ${ethers.formatUnits(buyAmount, quoteTokenDecimals)}, 实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}));
|
|
552
|
+
// ✅ 计算利润
|
|
553
|
+
const tokenProfitAmount = calculateProfitAmount(quote.quotedNative);
|
|
554
|
+
let nativeProfitAmount = tokenProfitAmount;
|
|
555
|
+
if (!useNativeToken && tokenProfitAmount > 0n) {
|
|
556
|
+
nativeProfitAmount = await getTokenToNativeQuote(chainContext.provider, quoteToken, tokenProfitAmount, chainContext.chainId);
|
|
557
|
+
}
|
|
558
|
+
// ✅ 并行构建所有买入交易
|
|
559
|
+
const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
|
|
560
|
+
const [sellUnsigned, ...buyUnsignedList] = await Promise.all([
|
|
561
|
+
// 卖出交易
|
|
562
|
+
portalSeller.swapExactInput.populateTransaction({
|
|
563
|
+
inputToken: tokenAddress,
|
|
564
|
+
outputToken,
|
|
565
|
+
inputAmount: sellAmountWei,
|
|
566
|
+
minOutputAmount: 0,
|
|
567
|
+
permitData: '0x'
|
|
568
|
+
}),
|
|
569
|
+
// 所有买入交易
|
|
570
|
+
...buyers.map((buyer, i) => {
|
|
571
|
+
const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
|
|
572
|
+
const buyAmount = buyAmountsWei[i];
|
|
573
|
+
return portalBuyer.swapExactInput.populateTransaction({
|
|
574
|
+
inputToken: outputToken,
|
|
575
|
+
outputToken: tokenAddress,
|
|
576
|
+
inputAmount: buyAmount,
|
|
577
|
+
minOutputAmount: 0,
|
|
578
|
+
permitData: '0x'
|
|
579
|
+
}, useNativeToken ? { value: buyAmount } : {});
|
|
580
|
+
})
|
|
581
|
+
]);
|
|
582
|
+
// ✅ 并行获取所有 nonce
|
|
583
|
+
const sellNonce = await nonceManager.getNextNonce(seller);
|
|
584
|
+
const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
|
|
585
|
+
// 利润交易 nonce(从第一个买方发送)
|
|
586
|
+
let profitTx = null;
|
|
587
|
+
if (nativeProfitAmount > 0n) {
|
|
588
|
+
const profitPayer = buyers[0];
|
|
589
|
+
const profitNonce = await nonceManager.getNextNonce(profitPayer);
|
|
590
|
+
profitTx = await profitPayer.signTransaction({
|
|
591
|
+
to: getProfitRecipient(),
|
|
592
|
+
value: nativeProfitAmount,
|
|
593
|
+
nonce: profitNonce,
|
|
594
|
+
gasPrice,
|
|
595
|
+
gasLimit: 21000n,
|
|
596
|
+
chainId: chainContext.chainId,
|
|
597
|
+
type: txType
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
nonceManager.clearTemp();
|
|
601
|
+
// ✅ 并行签名所有交易
|
|
602
|
+
const sellTx = buildTransactionRequest(sellUnsigned, {
|
|
603
|
+
from: seller.address,
|
|
604
|
+
nonce: sellNonce,
|
|
605
|
+
gasLimit: finalGasLimit,
|
|
606
|
+
gasPrice,
|
|
607
|
+
priorityFee,
|
|
608
|
+
chainId: chainContext.chainId,
|
|
609
|
+
txType,
|
|
610
|
+
value: 0n
|
|
611
|
+
});
|
|
612
|
+
const [signedSell, ...signedBuys] = await Promise.all([
|
|
613
|
+
seller.signTransaction(sellTx),
|
|
614
|
+
...buyers.map((buyer, i) => {
|
|
615
|
+
const buyTx = buildTransactionRequest(buyUnsignedList[i], {
|
|
616
|
+
from: buyer.address,
|
|
617
|
+
nonce: buyerNonces[i],
|
|
618
|
+
gasLimit: finalGasLimit,
|
|
619
|
+
gasPrice,
|
|
620
|
+
priorityFee,
|
|
621
|
+
chainId: chainContext.chainId,
|
|
622
|
+
txType,
|
|
623
|
+
value: useNativeToken ? buyAmountsWei[i] : 0n
|
|
624
|
+
});
|
|
625
|
+
return buyer.signTransaction(buyTx);
|
|
626
|
+
})
|
|
627
|
+
]);
|
|
628
|
+
// ✅ 按顺序组装交易数组
|
|
629
|
+
const signedTransactions = [];
|
|
630
|
+
if (approvalTx)
|
|
631
|
+
signedTransactions.push(approvalTx);
|
|
632
|
+
signedTransactions.push(signedSell);
|
|
633
|
+
signedTransactions.push(...signedBuys);
|
|
634
|
+
if (profitTx)
|
|
635
|
+
signedTransactions.push(profitTx);
|
|
636
|
+
console.log(`✅ Flap 批量换手签名完成: ${signedTransactions.length} 笔交易`);
|
|
637
|
+
return {
|
|
638
|
+
signedTransactions,
|
|
639
|
+
metadata: {
|
|
640
|
+
sellerAddress: seller.address,
|
|
641
|
+
buyerAddresses: buyers.map(b => b.address),
|
|
642
|
+
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
643
|
+
buyAmounts: buyAmountsWei.map(amt => useNativeToken
|
|
644
|
+
? ethers.formatEther(amt)
|
|
645
|
+
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
646
|
+
hasApproval: !!approvalTx,
|
|
647
|
+
profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -35,8 +35,8 @@ export { validatePrivateKeys, type PrivateKeyValidation } from './utils/wallet.j
|
|
|
35
35
|
export { stealthTransfer, type StealthTransferResult, type StealthTransferSimpleParams } from './utils/stealth-transfer.js';
|
|
36
36
|
export { inspectTokenLP, getFactoryFromRouter, registerDYORSwap, registerDex, getChainConfig, type LPInfo, type LPPlatform, type InspectOptions, type DexConfig, type ChainDexConfig, } from './utils/lp-inspect.js';
|
|
37
37
|
export { disperseWithBundle, sweepWithBundle, type DisperseSignParams, type SweepSignParams, type SignedTransactionsResult, type DisperseParams, type SweepParams, type BundleSubmitResult } from './utils/airdrop-sweep.js';
|
|
38
|
-
export { fourBundleSwapMerkle, type FourSwapSignConfig, type FourSwapConfig, type FourBundleSwapSignParams, type FourBundleSwapParams, type FourSwapResult } from './contracts/tm-bundle-merkle/swap.js';
|
|
39
|
-
export { flapBundleSwapMerkle, type FlapSwapSignConfig, type FlapSwapConfig, type FlapBundleSwapSignParams, type FlapBundleSwapParams, type FlapSwapResult } from './flap/portal-bundle-merkle/swap.js';
|
|
38
|
+
export { fourBundleSwapMerkle, fourBatchSwapMerkle, type FourSwapSignConfig, type FourSwapConfig, type FourBundleSwapSignParams, type FourBundleSwapParams, type FourSwapResult, type FourBatchSwapSignParams, type FourBatchSwapResult } from './contracts/tm-bundle-merkle/swap.js';
|
|
39
|
+
export { flapBundleSwapMerkle, flapBatchSwapMerkle, type FlapSwapSignConfig, type FlapSwapConfig, type FlapBundleSwapSignParams, type FlapBundleSwapParams, type FlapSwapResult, type FlapBatchSwapSignParams, type FlapBatchSwapResult } from './flap/portal-bundle-merkle/swap.js';
|
|
40
40
|
export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, type PancakeSwapSignConfig, type PancakeBundleSwapSignParams, type PancakeSwapConfig, type PancakeBundleSwapParams, type PancakeSwapResult, type PancakeBatchSwapSignParams, type PancakeBatchSwapResult, type SwapRouteType, type V2RouteParams, type V3SingleRouteParams, type V3MultiRouteParams, type RouteParams } from './pancake/bundle-swap.js';
|
|
41
41
|
export { fourBundleBuyFirstMerkle, type FourBuyFirstConfig, type FourBundleBuyFirstParams, type FourBuyFirstSignConfig, type FourBundleBuyFirstSignParams, type FourBuyFirstResult } from './contracts/tm-bundle-merkle/swap-buy-first.js';
|
|
42
42
|
export { flapBundleBuyFirstMerkle, type FlapBuyFirstSignConfig, type FlapBuyFirstConfig, type FlapBundleBuyFirstSignParams, type FlapBundleBuyFirstParams, type FlapBuyFirstResult } from './flap/portal-bundle-merkle/swap-buy-first.js';
|
package/dist/index.js
CHANGED
|
@@ -51,9 +51,9 @@ export { disperseWithBundle, sweepWithBundle } from './utils/airdrop-sweep.js';
|
|
|
51
51
|
// 捆绑换手功能(Bundle Swap)
|
|
52
52
|
// ============================================================
|
|
53
53
|
// Four内盘换手
|
|
54
|
-
export { fourBundleSwapMerkle } from './contracts/tm-bundle-merkle/swap.js';
|
|
54
|
+
export { fourBundleSwapMerkle, fourBatchSwapMerkle } from './contracts/tm-bundle-merkle/swap.js';
|
|
55
55
|
// Flap内盘换手
|
|
56
|
-
export { flapBundleSwapMerkle } from './flap/portal-bundle-merkle/swap.js';
|
|
56
|
+
export { flapBundleSwapMerkle, flapBatchSwapMerkle } from './flap/portal-bundle-merkle/swap.js';
|
|
57
57
|
// PancakeSwap V2/V3通用换手
|
|
58
58
|
export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle } from './pancake/bundle-swap.js';
|
|
59
59
|
// 先买后卖(Buy-First)入口
|
|
@@ -458,40 +458,50 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
458
458
|
console.log('📊 预估卖出所得:', ethers.formatEther(estimatedBNBOut), 'BNB');
|
|
459
459
|
// ✅ 计算每个买方的买入金额
|
|
460
460
|
let buyAmountsWei;
|
|
461
|
+
const totalBuyAmount = applySlippage(estimatedBNBOut, slippageTolerance);
|
|
461
462
|
if (buyerAmounts && buyerAmounts.length === buyers.length) {
|
|
462
|
-
//
|
|
463
|
+
// 方式1:使用指定的买入金额(USDT)
|
|
463
464
|
buyAmountsWei = buyerAmounts.map(amt => useNativeToken
|
|
464
465
|
? ethers.parseEther(amt)
|
|
465
466
|
: ethers.parseUnits(amt, quoteTokenDecimals));
|
|
466
467
|
}
|
|
468
|
+
else if (params.buyerRatios && params.buyerRatios.length === buyers.length) {
|
|
469
|
+
// ✅ 方式2:按比例分配卖出所得
|
|
470
|
+
// buyerRatios 如 [0.3, 0.5, 0.2] 表示第一个买方分 30%,第二个 50%,第三个 20%
|
|
471
|
+
console.log('📊 使用比例分配,卖出所得:', ethers.formatEther(totalBuyAmount), 'BNB');
|
|
472
|
+
console.log('📊 买方比例:', params.buyerRatios);
|
|
473
|
+
buyAmountsWei = params.buyerRatios.map((ratio, index) => {
|
|
474
|
+
// 按比例计算每个买方的金额
|
|
475
|
+
const amount = (totalBuyAmount * BigInt(Math.round(ratio * 10000))) / 10000n;
|
|
476
|
+
console.log(` 买方 ${index + 1}: ${(ratio * 100).toFixed(1)}% = ${ethers.formatEther(amount)} BNB`);
|
|
477
|
+
return amount;
|
|
478
|
+
});
|
|
479
|
+
}
|
|
467
480
|
else {
|
|
468
|
-
//
|
|
469
|
-
const totalBuyAmount = applySlippage(estimatedBNBOut, slippageTolerance);
|
|
481
|
+
// 方式3:平均分配
|
|
470
482
|
const amountPerBuyer = totalBuyAmount / BigInt(buyers.length);
|
|
471
483
|
buyAmountsWei = buyers.map(() => amountPerBuyer);
|
|
472
484
|
}
|
|
473
|
-
// ✅
|
|
485
|
+
// ✅ 并行验证所有买方余额
|
|
474
486
|
const reserveGas = ethers.parseEther((config.reserveGasBNB ?? 0.0005).toString());
|
|
475
|
-
|
|
476
|
-
|
|
487
|
+
const erc20Contract = useNativeToken ? null : new Contract(quoteToken, ERC20_BALANCE_OF_ABI, context.provider);
|
|
488
|
+
await Promise.all(buyers.map(async (buyer, i) => {
|
|
477
489
|
const buyAmount = buyAmountsWei[i];
|
|
478
|
-
let buyerBalance;
|
|
479
490
|
if (useNativeToken) {
|
|
480
|
-
buyerBalance = await buyer.provider.getBalance(buyer.address);
|
|
491
|
+
const buyerBalance = await buyer.provider.getBalance(buyer.address);
|
|
481
492
|
const required = buyAmount + FLAT_FEE + reserveGas;
|
|
482
493
|
if (buyerBalance < required) {
|
|
483
494
|
throw new Error(`买方 ${i + 1} 余额不足: 需要 ${ethers.formatEther(required)} BNB, 实际 ${ethers.formatEther(buyerBalance)} BNB`);
|
|
484
495
|
}
|
|
485
496
|
}
|
|
486
497
|
else {
|
|
487
|
-
const
|
|
488
|
-
buyerBalance = await erc20.balanceOf(buyer.address);
|
|
498
|
+
const buyerBalance = await erc20Contract.balanceOf(buyer.address);
|
|
489
499
|
const required = buyAmount + FLAT_FEE;
|
|
490
500
|
if (buyerBalance < required) {
|
|
491
501
|
throw new Error(`买方 ${i + 1} 代币余额不足: 需要 ${ethers.formatUnits(required, quoteTokenDecimals)}, 实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
|
|
492
502
|
}
|
|
493
503
|
}
|
|
494
|
-
}
|
|
504
|
+
}));
|
|
495
505
|
// ✅ 构建卖出交易
|
|
496
506
|
const proxySeller = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, seller);
|
|
497
507
|
const deadline = Math.floor(Date.now() / 1000) + 600;
|
|
@@ -508,49 +518,35 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
508
518
|
const { v3LpAddresses, v3ExactTokenIn } = routeParams;
|
|
509
519
|
sellUnsigned = await proxySeller.swapV3MultiHop.populateTransaction(v3LpAddresses, v3ExactTokenIn, sellAmountWei, 0n, seller.address, { value: FLAT_FEE });
|
|
510
520
|
}
|
|
511
|
-
// ✅
|
|
512
|
-
const buyUnsignedList =
|
|
513
|
-
for (let i = 0; i < buyers.length; i++) {
|
|
514
|
-
const buyer = buyers[i];
|
|
521
|
+
// ✅ 并行构建多个买入交易
|
|
522
|
+
const buyUnsignedList = await Promise.all(buyers.map(async (buyer, i) => {
|
|
515
523
|
const buyAmount = buyAmountsWei[i];
|
|
516
524
|
const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
|
|
517
525
|
const buyValue = useNativeToken ? buyAmount + FLAT_FEE : FLAT_FEE;
|
|
518
|
-
let buyUnsigned;
|
|
519
526
|
if (routeParams.routeType === 'v2') {
|
|
520
527
|
const { v2Path } = routeParams;
|
|
521
528
|
const reversePath = [...v2Path].reverse();
|
|
522
|
-
|
|
529
|
+
return await proxyBuyer.swapV2.populateTransaction(buyAmount, 0n, reversePath, buyer.address, deadline, { value: buyValue });
|
|
523
530
|
}
|
|
524
531
|
else if (routeParams.routeType === 'v3-single') {
|
|
525
532
|
const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
|
|
526
|
-
|
|
533
|
+
return await proxyBuyer.swapV3Single.populateTransaction(v3TokenOut, // 买入时反向
|
|
527
534
|
v3TokenIn, v3Fee, buyAmount, 0n, buyer.address, { value: buyValue });
|
|
528
535
|
}
|
|
529
536
|
else {
|
|
530
537
|
const { v3LpAddresses } = routeParams;
|
|
531
538
|
const exactTokenOut = tokenAddress;
|
|
532
|
-
|
|
539
|
+
return await proxyBuyer.swapV3MultiHop.populateTransaction(v3LpAddresses, exactTokenOut, buyAmount, 0n, buyer.address, { value: buyValue });
|
|
533
540
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
// ✅ 规划 Nonce
|
|
541
|
+
}));
|
|
542
|
+
// ✅ 规划 Nonce(并行获取所有钱包的 nonce)
|
|
537
543
|
// 卖方: [授权(可选)] → [卖出]
|
|
538
|
-
const sellerNonces = [];
|
|
539
|
-
if (approvalTx) {
|
|
540
|
-
// 授权已经在 ensureSellerApproval 中消耗了一个 nonce
|
|
541
|
-
}
|
|
542
544
|
const sellNonce = await nonceManager.getNextNonce(seller);
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
for (const buyer of buyers) {
|
|
547
|
-
const nonce = await nonceManager.getNextNonce(buyer);
|
|
548
|
-
buyerNonces.push(nonce);
|
|
549
|
-
}
|
|
550
|
-
// 利润交易:从第一个买方发送(或卖方,取决于谁有足够余额)
|
|
545
|
+
// ✅ 并行获取所有买方的 nonce
|
|
546
|
+
const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
|
|
547
|
+
// 利润交易:从第一个买方发送
|
|
551
548
|
const profitAmount = calculateProfitAmount(estimatedBNBOut);
|
|
552
549
|
let profitTx = null;
|
|
553
|
-
let profitPayerIndex = 0; // 默认第一个买方支付利润
|
|
554
550
|
if (profitAmount > 0n) {
|
|
555
551
|
// 选择第一个买方作为利润支付者
|
|
556
552
|
const profitPayer = buyers[0];
|
|
@@ -566,14 +562,9 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
566
562
|
});
|
|
567
563
|
}
|
|
568
564
|
nonceManager.clearTemp();
|
|
569
|
-
// ✅
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (approvalTx) {
|
|
573
|
-
signedTransactions.push(approvalTx);
|
|
574
|
-
}
|
|
575
|
-
// 2. 卖出交易
|
|
576
|
-
const signedSell = await seller.signTransaction({
|
|
565
|
+
// ✅ 并行签名所有交易
|
|
566
|
+
// 1. 签名卖出交易
|
|
567
|
+
const signedSellPromise = seller.signTransaction({
|
|
577
568
|
...sellUnsigned,
|
|
578
569
|
from: seller.address,
|
|
579
570
|
nonce: sellNonce,
|
|
@@ -582,27 +573,29 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
582
573
|
chainId: context.chainId,
|
|
583
574
|
type: txType
|
|
584
575
|
});
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
576
|
+
// 2. 并行签名所有买入交易
|
|
577
|
+
const signedBuyPromises = buyers.map((buyer, i) => buyer.signTransaction({
|
|
578
|
+
...buyUnsignedList[i],
|
|
579
|
+
from: buyer.address,
|
|
580
|
+
nonce: buyerNonces[i],
|
|
581
|
+
gasLimit: finalGasLimit,
|
|
582
|
+
gasPrice,
|
|
583
|
+
chainId: context.chainId,
|
|
584
|
+
type: txType
|
|
585
|
+
}));
|
|
586
|
+
// 3. 等待所有签名完成
|
|
587
|
+
const [signedSell, ...signedBuys] = await Promise.all([
|
|
588
|
+
signedSellPromise,
|
|
589
|
+
...signedBuyPromises
|
|
590
|
+
]);
|
|
591
|
+
// 4. 按顺序组装交易数组
|
|
592
|
+
const signedTransactions = [];
|
|
593
|
+
if (approvalTx)
|
|
594
|
+
signedTransactions.push(approvalTx); // 授权(如果有)
|
|
595
|
+
signedTransactions.push(signedSell); // 卖出
|
|
596
|
+
signedTransactions.push(...signedBuys); // 多个买入
|
|
597
|
+
if (profitTx)
|
|
598
|
+
signedTransactions.push(profitTx); // 利润(最后)
|
|
606
599
|
console.log(`✅ 批量换手签名完成: ${signedTransactions.length} 笔交易`);
|
|
607
600
|
console.log(` - 授权: ${approvalTx ? 1 : 0}, 卖出: 1, 买入: ${buyers.length}, 利润: ${profitTx ? 1 : 0}`);
|
|
608
601
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "four-flap-meme-sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.73",
|
|
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
|
+
}
|