four-flap-meme-sdk 1.4.65 → 1.4.67

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.
@@ -105,22 +105,34 @@ export async function fourBundleBuyFirstMerkle(params) {
105
105
  if (buyerFundsWei <= 0n) {
106
106
  throw new Error('buyerFunds 需要大于 0');
107
107
  }
108
- // ✅ 优化:第二批并行 - buyQuote、sellerTokenBal、decimals
108
+ // ✅ 优化:第二批并行 - buyQuote、sellerTokenBal、decimals、allowance
109
109
  const helper3 = new Contract(ADDRESSES.BSC.TokenManagerHelper3, HELPER3_ABI, provider);
110
110
  const erc20 = new Contract(tokenAddress, [
111
111
  'function balanceOf(address) view returns (uint256)',
112
- 'function decimals() view returns (uint8)'
112
+ 'function decimals() view returns (uint8)',
113
+ 'function allowance(address,address) view returns (uint256)'
113
114
  ], provider);
114
- const [buyQuote, sellerTokenBal, decimals] = await Promise.all([
115
+ const [buyQuote, sellerTokenBal, decimals, currentAllowance] = await Promise.all([
115
116
  helper3.tryBuy(tokenAddress, 0n, buyerFundsWei),
116
117
  erc20.balanceOf(seller.address),
117
- erc20.decimals()
118
+ erc20.decimals(),
119
+ erc20.allowance(seller.address, TM_ADDRESS)
118
120
  ]);
121
+ // ✅ 检查是否需要授权(授权额度小于 10 亿代币时需要授权)
122
+ const APPROVAL_THRESHOLD = ethers.parseUnits('1000000000', decimals);
123
+ const needApproval = currentAllowance < APPROVAL_THRESHOLD;
119
124
  const estimatedTokenAmount = buyQuote.estimatedAmount ?? buyQuote[2];
120
125
  if (!estimatedTokenAmount || estimatedTokenAmount <= 0n) {
121
126
  throw new Error('报价失败:无法估算可买入的代币数量');
122
127
  }
123
- const sellAmountWei = estimatedTokenAmount;
128
+ // 多笔买入时减少卖出数量以应对滑点
129
+ // 原因:分批买入会推高价格,实际获得的代币 < 一次性买入的报价
130
+ // 每多一笔买入,减少 0.5% 的卖出数量(保守估计)
131
+ let sellAmountWei = estimatedTokenAmount;
132
+ if (buyCount > 1) {
133
+ const slippageReductionBps = BigInt((buyCount - 1) * 50); // 每多1笔减少 0.5% = 50 bps
134
+ sellAmountWei = estimatedTokenAmount * (10000n - slippageReductionBps) / 10000n;
135
+ }
124
136
  // 卖方余额检查
125
137
  if (!sameAddress && sellerTokenBal < sellAmountWei) {
126
138
  throw new Error(`卖方代币余额不足: 需要 ${ethers.formatUnits(sellAmountWei, decimals)},实际 ${ethers.formatUnits(sellerTokenBal, decimals)}`);
@@ -139,7 +151,7 @@ export async function fourBundleBuyFirstMerkle(params) {
139
151
  // ✅ 构建多笔买入和卖出交易
140
152
  const buyUnsignedPromises = buyAmountsWei.map(amount => tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, amount, 0n, { value: amount }));
141
153
  const sellUnsignedPromises = sellAmountsWei.map(amount => tmSeller.sellToken.populateTransaction(0n, tokenAddress, amount, 0n));
142
- // ✅ 规划多笔交易 nonce
154
+ // ✅ 规划多笔交易 nonce(包含授权交易)
143
155
  const multiNoncePlan = await planMultiNonces({
144
156
  buyer,
145
157
  seller,
@@ -147,6 +159,7 @@ export async function fourBundleBuyFirstMerkle(params) {
147
159
  sellCount,
148
160
  extractProfit,
149
161
  needBribeTx,
162
+ needApproval, // ✅ 新增
150
163
  sameAddress,
151
164
  nonceManager
152
165
  });
@@ -191,16 +204,35 @@ export async function fourBundleBuyFirstMerkle(params) {
191
204
  type: txType,
192
205
  value: 0n
193
206
  }));
207
+ // ✅ 授权交易(放在买入后、卖出前)
208
+ let approvalTx = null;
209
+ if (needApproval && multiNoncePlan.approvalNonce !== undefined) {
210
+ const approveInterface = new ethers.Interface(['function approve(address,uint256) returns (bool)']);
211
+ const approveData = approveInterface.encodeFunctionData('approve', [TM_ADDRESS, ethers.MaxUint256]);
212
+ approvalTx = await seller.signTransaction({
213
+ to: tokenAddress,
214
+ data: approveData,
215
+ value: 0n,
216
+ nonce: multiNoncePlan.approvalNonce,
217
+ gasLimit: 80000n,
218
+ gasPrice,
219
+ chainId: chainIdNum,
220
+ type: txType
221
+ });
222
+ }
194
223
  const [signedBuys, signedSells] = await Promise.all([
195
224
  Promise.all(buySignPromises),
196
225
  Promise.all(sellSignPromises)
197
226
  ]);
198
227
  nonceManager.clearTemp();
199
- // ✅ 组装交易列表:贿赂 → 买入(多笔) → 卖出(多笔)
228
+ // ✅ 组装交易列表:贿赂 → 买入(多笔) → 授权 → 卖出(多笔)
200
229
  const allTransactions = [];
201
230
  if (bribeTx)
202
231
  allTransactions.push(bribeTx);
203
- allTransactions.push(...signedBuys, ...signedSells);
232
+ allTransactions.push(...signedBuys);
233
+ if (approvalTx)
234
+ allTransactions.push(approvalTx);
235
+ allTransactions.push(...signedSells);
204
236
  // ✅ 利润多跳转账(强制 2 跳中转)
205
237
  let profitHopWallets;
206
238
  if (extractProfit && profitAmount > 0n && multiNoncePlan.profitNonce !== undefined) {
@@ -235,33 +267,38 @@ export async function fourBundleBuyFirstMerkle(params) {
235
267
  }
236
268
  /**
237
269
  * ✅ 规划多笔交易 nonce
238
- * 交易顺序:贿赂 → 买入(多笔) → 卖出(多笔) → 利润
270
+ * 交易顺序:贿赂 → 买入(多笔) → 授权 → 卖出(多笔) → 利润
239
271
  */
240
- async function planMultiNonces({ buyer, seller, buyCount, sellCount, extractProfit, needBribeTx, sameAddress, nonceManager }) {
272
+ async function planMultiNonces({ buyer, seller, buyCount, sellCount, extractProfit, needBribeTx, needApproval, sameAddress, nonceManager }) {
241
273
  const profitNonceCount = extractProfit ? 1 : 0;
274
+ const approvalTxCount = needApproval ? 1 : 0;
242
275
  if (sameAddress) {
276
+ // 同地址:贿赂 → 买入(多笔) → 授权 → 卖出(多笔) → 利润
243
277
  const bribeTxCount = needBribeTx ? 1 : 0;
244
- const totalTxCount = bribeTxCount + buyCount + sellCount + profitNonceCount;
278
+ const totalTxCount = bribeTxCount + buyCount + approvalTxCount + sellCount + profitNonceCount;
245
279
  const nonces = await nonceManager.getNextNonceBatch(buyer, totalTxCount);
246
280
  let idx = 0;
247
281
  const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
248
282
  const buyerNonces = nonces.slice(idx, idx + buyCount);
249
283
  idx += buyCount;
284
+ const approvalNonce = needApproval ? nonces[idx++] : undefined;
250
285
  const sellerNonces = nonces.slice(idx, idx + sellCount);
251
286
  idx += sellCount;
252
287
  const profitNonce = extractProfit ? nonces[idx] : undefined;
253
- return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
288
+ return { buyerNonces, sellerNonces, bribeNonce, approvalNonce, profitNonce };
254
289
  }
290
+ // 不同地址:买方只负责买入,卖方负责贿赂+授权+卖出+利润
255
291
  const bribeTxCount = needBribeTx ? 1 : 0;
256
- const sellerTxCount = bribeTxCount + sellCount + profitNonceCount;
292
+ const sellerTxCount = bribeTxCount + approvalTxCount + sellCount + profitNonceCount;
257
293
  const [buyerNonces, sellerNoncesAll] = await Promise.all([
258
294
  nonceManager.getNextNonceBatch(buyer, buyCount),
259
295
  nonceManager.getNextNonceBatch(seller, sellerTxCount)
260
296
  ]);
261
297
  let idx = 0;
262
298
  const bribeNonce = needBribeTx ? sellerNoncesAll[idx++] : undefined;
299
+ const approvalNonce = needApproval ? sellerNoncesAll[idx++] : undefined;
263
300
  const sellerNonces = sellerNoncesAll.slice(idx, idx + sellCount);
264
301
  idx += sellCount;
265
302
  const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
266
- return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
303
+ return { buyerNonces, sellerNonces, bribeNonce, approvalNonce, profitNonce };
267
304
  }
@@ -121,9 +121,31 @@ export async function pancakeBundleBuyFirstMerkle(params) {
121
121
  buyerFundsWei: buyerFundsInfo.buyerFundsWei,
122
122
  provider: context.provider
123
123
  });
124
+ // ✅ 检查是否需要授权(卖出代币需要先授权给 Router)
125
+ const approvalTarget = routeParams.routeType === 'v2'
126
+ ? PANCAKE_V2_ROUTER_ADDRESS
127
+ : PANCAKE_V3_ROUTER_ADDRESS;
128
+ const erc20ForApproval = new Contract(tokenAddress, [
129
+ 'function allowance(address,address) view returns (uint256)',
130
+ 'function decimals() view returns (uint8)'
131
+ ], context.provider);
132
+ const [currentAllowance, tokenDecimals] = await Promise.all([
133
+ erc20ForApproval.allowance(seller.address, approvalTarget),
134
+ erc20ForApproval.decimals()
135
+ ]);
136
+ const APPROVAL_THRESHOLD = ethers.parseUnits('1000000000', tokenDecimals);
137
+ const needApproval = currentAllowance < APPROVAL_THRESHOLD;
138
+ // ✅ 多笔买入时减少卖出数量以应对滑点
139
+ // 原因:分批买入会推高价格,实际获得的代币 < 一次性买入的报价
140
+ // 每多一笔买入,减少 0.5% 的卖出数量(保守估计)
141
+ let adjustedSellAmount = quoteResult.quotedTokenOut;
142
+ if (buyCount > 1) {
143
+ const slippageReductionBps = BigInt((buyCount - 1) * 50); // 每多1笔减少 0.5% = 50 bps
144
+ adjustedSellAmount = quoteResult.quotedTokenOut * (10000n - slippageReductionBps) / 10000n;
145
+ }
124
146
  // ✅ 拆分买入和卖出金额
125
147
  const buyAmountsWei = splitAmount(buyerFundsInfo.buyerFundsWei, buyCount);
126
- const sellAmountsWei = splitAmount(quoteResult.quotedTokenOut, sellCount);
148
+ const sellAmountsWei = splitAmount(adjustedSellAmount, sellCount);
127
149
  // ✅ 构建多笔买入和卖出交易
128
150
  const swapUnsignedArray = await buildMultiRouteTransactions({
129
151
  routeParams,
@@ -173,6 +195,7 @@ export async function pancakeBundleBuyFirstMerkle(params) {
173
195
  sameAddress,
174
196
  extractProfit: profitAmount > 0n,
175
197
  needBribeTx,
198
+ needApproval, // ✅ 新增
176
199
  nonceManager
177
200
  });
178
201
  // ✅ 贿赂交易放在首位(由卖方发送,与利润交易同一钱包)
@@ -198,6 +221,22 @@ export async function pancakeBundleBuyFirstMerkle(params) {
198
221
  chainId: context.chainId,
199
222
  type: txType
200
223
  })));
224
+ // ✅ 授权交易(放在买入后、卖出前)
225
+ let approvalTx = null;
226
+ if (needApproval && multiNoncePlan.approvalNonce !== undefined) {
227
+ const approveInterface = new ethers.Interface(['function approve(address,uint256) returns (bool)']);
228
+ const approveData = approveInterface.encodeFunctionData('approve', [approvalTarget, ethers.MaxUint256]);
229
+ approvalTx = await seller.signTransaction({
230
+ to: tokenAddress,
231
+ data: approveData,
232
+ value: 0n,
233
+ nonce: multiNoncePlan.approvalNonce,
234
+ gasLimit: 80000n,
235
+ gasPrice,
236
+ chainId: context.chainId,
237
+ type: txType
238
+ });
239
+ }
201
240
  // ✅ 并行签名所有卖出交易
202
241
  const signedSells = await Promise.all(swapUnsignedArray.sellUnsignedArray.map((unsigned, i) => seller.signTransaction({
203
242
  ...unsigned,
@@ -221,11 +260,14 @@ export async function pancakeBundleBuyFirstMerkle(params) {
221
260
  provider: context.provider,
222
261
  buyerAddress: buyer.address
223
262
  });
224
- // ✅ 组装顺序:贿赂 → 买入(多笔) → 卖出(多笔)
263
+ // ✅ 组装顺序:贿赂 → 买入(多笔) → 授权 → 卖出(多笔)
225
264
  const allTransactions = [];
226
265
  if (bribeTx)
227
266
  allTransactions.push(bribeTx);
228
- allTransactions.push(...signedBuys, ...signedSells);
267
+ allTransactions.push(...signedBuys);
268
+ if (approvalTx)
269
+ allTransactions.push(approvalTx);
270
+ allTransactions.push(...signedSells);
229
271
  // ✅ 利润多跳转账(强制 2 跳中转)
230
272
  const profitResult = await buildProfitTransaction({
231
273
  provider: context.provider,
@@ -536,35 +578,41 @@ async function planNonces({ buyer, seller, sameAddress, extractProfit, needBribe
536
578
  }
537
579
  /**
538
580
  * ✅ 规划多笔交易 nonce
539
- * 交易顺序:贿赂 → 买入(多笔) → 卖出(多笔) → 利润
581
+ * 交易顺序:贿赂 → 买入(多笔) → 授权 → 卖出(多笔) → 利润
540
582
  */
541
- async function planMultiNonces({ buyer, seller, buyCount, sellCount, sameAddress, extractProfit, needBribeTx, nonceManager }) {
583
+ async function planMultiNonces({ buyer, seller, buyCount, sellCount, sameAddress, extractProfit, needBribeTx, needApproval, // ✅ 新增
584
+ nonceManager }) {
542
585
  const profitNonceCount = extractProfit ? 1 : 0;
586
+ const approvalTxCount = needApproval ? 1 : 0;
543
587
  if (sameAddress) {
588
+ // 同地址:贿赂 → 买入(多笔) → 授权 → 卖出(多笔) → 利润
544
589
  const bribeTxCount = needBribeTx ? 1 : 0;
545
- const totalTxCount = bribeTxCount + buyCount + sellCount + profitNonceCount;
590
+ const totalTxCount = bribeTxCount + buyCount + approvalTxCount + sellCount + profitNonceCount;
546
591
  const nonces = await nonceManager.getNextNonceBatch(buyer, totalTxCount);
547
592
  let idx = 0;
548
593
  const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
549
594
  const buyerNonces = nonces.slice(idx, idx + buyCount);
550
595
  idx += buyCount;
596
+ const approvalNonce = needApproval ? nonces[idx++] : undefined;
551
597
  const sellerNonces = nonces.slice(idx, idx + sellCount);
552
598
  idx += sellCount;
553
599
  const profitNonce = extractProfit ? nonces[idx] : undefined;
554
- return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
600
+ return { buyerNonces, sellerNonces, bribeNonce, approvalNonce, profitNonce };
555
601
  }
602
+ // 不同地址:买方只负责买入,卖方负责贿赂+授权+卖出+利润
556
603
  const bribeTxCount = needBribeTx ? 1 : 0;
557
- const sellerTxCount = bribeTxCount + sellCount + profitNonceCount;
604
+ const sellerTxCount = bribeTxCount + approvalTxCount + sellCount + profitNonceCount;
558
605
  const [buyerNonces, sellerNoncesAll] = await Promise.all([
559
606
  nonceManager.getNextNonceBatch(buyer, buyCount),
560
607
  nonceManager.getNextNonceBatch(seller, sellerTxCount)
561
608
  ]);
562
609
  let idx = 0;
563
610
  const bribeNonce = needBribeTx ? sellerNoncesAll[idx++] : undefined;
611
+ const approvalNonce = needApproval ? sellerNoncesAll[idx++] : undefined;
564
612
  const sellerNonces = sellerNoncesAll.slice(idx, idx + sellCount);
565
613
  idx += sellCount;
566
614
  const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
567
- return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
615
+ return { buyerNonces, sellerNonces, bribeNonce, approvalNonce, profitNonce };
568
616
  }
569
617
  /**
570
618
  * 构建利润多跳转账交易(强制 2 跳中转)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.65",
3
+ "version": "1.4.67",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",