four-flap-meme-sdk 1.4.64 → 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
|
-
|
|
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
|
|
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
|
}
|
|
@@ -160,8 +160,10 @@ export async function flapBundleBuyFirstMerkle(params) {
|
|
|
160
160
|
// ✅ 拆分买入和卖出金额
|
|
161
161
|
const buyAmountsWei = splitAmount(buyerFundsWei, buyCount);
|
|
162
162
|
const sellAmountsWei = splitAmount(sellAmountWei, sellCount);
|
|
163
|
-
// ✅
|
|
164
|
-
|
|
163
|
+
// ✅ 多笔买入时,minOutputAmount 设置为 0 以避免滑点导致交易失败
|
|
164
|
+
// 原因:每笔买入会改变池子价格,后续买入获得的代币会变少
|
|
165
|
+
// 单笔买入时使用正常的 minOutputAmount 作为保护
|
|
166
|
+
const minOutputPerBuy = buyCount === 1 ? sellAmountWei : 0n;
|
|
165
167
|
// ✅ 第三批并行 - 构建多笔买入和卖出交易、estimatedSellFunds
|
|
166
168
|
const buyUnsignedPromises = buyAmountsWei.map(amount => portalBuyer.swapExactInput.populateTransaction({
|
|
167
169
|
inputToken,
|
|
@@ -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(
|
|
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
|
|
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,
|
|
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 跳中转)
|