four-flap-meme-sdk 1.3.80 → 1.3.82

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.
@@ -1,6 +1,6 @@
1
1
  import { ethers, Wallet } from 'ethers';
2
2
  import { MerkleClient } from '../../clients/merkle.js';
3
- import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
3
+ import { getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
4
4
  import { FLAP_PORTAL_ADDRESSES } from '../constants.js';
5
5
  import { CHAIN_ID_MAP, PORTAL_ABI, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient } from './config.js';
6
6
  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
@@ -13,24 +13,26 @@ export async function flapPrivateBuyMerkle(params) {
13
13
  const { chainId, provider } = createMerkleContext(chain, config);
14
14
  const wallet = new Wallet(privateKey, provider);
15
15
  const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
16
- const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
17
16
  const originalAmountWei = ethers.parseEther(amountIn);
18
17
  // ✅ 利润提取配置
19
18
  const extractProfit = shouldExtractProfit(config);
20
19
  const { actualAmountWei, profitWei } = splitBuyAmount(originalAmountWei, extractProfit, config);
21
20
  const minAmountOut = parseMinOutput(minOutputAmount);
22
- const signedTxs = [];
23
21
  const portal = new ethers.Contract(portalAddr, PORTAL_ABI, wallet);
24
- const unsigned = await portal.swapExactInput.populateTransaction({
25
- inputToken: ZERO_ADDRESS,
26
- outputToken: tokenAddress,
27
- inputAmount: actualAmountWei, // ✅ 使用扣除利润后的金额
28
- minOutputAmount: minAmountOut,
29
- permitData: '0x',
30
- }, { value: actualAmountWei } // ✅ 使用扣除利润后的金额
31
- );
22
+ // 并行获取 gasPrice、nonce 和构建未签名交易
23
+ const [gasPrice, currentNonce, unsigned] = await Promise.all([
24
+ getOptimizedGasPrice(provider, getGasPriceConfig(config)),
25
+ wallet.getNonce(),
26
+ portal.swapExactInput.populateTransaction({
27
+ inputToken: ZERO_ADDRESS,
28
+ outputToken: tokenAddress,
29
+ inputAmount: actualAmountWei,
30
+ minOutputAmount: minAmountOut,
31
+ permitData: '0x',
32
+ }, { value: actualAmountWei })
33
+ ]);
32
34
  const gasLimit = resolveGasLimit(config.gasLimitMultiplier);
33
- const currentNonce = await wallet.getNonce();
35
+ const signedTxs = [];
34
36
  signedTxs.push(await wallet.signTransaction({
35
37
  ...unsigned,
36
38
  from: wallet.address,
@@ -39,7 +41,7 @@ export async function flapPrivateBuyMerkle(params) {
39
41
  gasPrice,
40
42
  chainId,
41
43
  type: getTxType(config),
42
- value: actualAmountWei // ✅ 使用扣除利润后的金额
44
+ value: actualAmountWei
43
45
  }));
44
46
  await appendSingleProfitTransfer({
45
47
  extractProfit,
@@ -61,11 +63,15 @@ export async function flapPrivateSellMerkle(params) {
61
63
  const { chainId, provider } = createMerkleContext(chain, config);
62
64
  const wallet = new Wallet(privateKey, provider);
63
65
  const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
64
- const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
65
66
  const amountWei = ethers.parseUnits(amount, 18);
66
- // ✅ 自动获取报价以计算预期收益和利润
67
67
  const portal = new ethers.Contract(portalAddr, PORTAL_ABI, wallet);
68
- const { quotedOutput, minOut } = await resolveSingleSellOutputs(portal, tokenAddress, amountWei, minOutputAmount);
68
+ // 并行获取 gasPrice、nonce 和报价
69
+ const [gasPrice, currentNonce, { quotedOutput, minOut }] = await Promise.all([
70
+ getOptimizedGasPrice(provider, getGasPriceConfig(config)),
71
+ wallet.getNonce(),
72
+ resolveSingleSellOutputs(portal, tokenAddress, amountWei, minOutputAmount)
73
+ ]);
74
+ // 构建未签名交易
69
75
  const unsigned = await portal.swapExactInput.populateTransaction({
70
76
  inputToken: tokenAddress,
71
77
  outputToken: ZERO_ADDRESS,
@@ -75,7 +81,6 @@ export async function flapPrivateSellMerkle(params) {
75
81
  });
76
82
  const gasLimit = resolveGasLimit(config.gasLimitMultiplier);
77
83
  const signedTxs = [];
78
- const currentNonce = await wallet.getNonce();
79
84
  signedTxs.push(await wallet.signTransaction({
80
85
  ...unsigned,
81
86
  from: wallet.address,
@@ -107,29 +112,35 @@ export async function flapBatchPrivateBuyMerkle(params) {
107
112
  }
108
113
  const { chainId, provider } = createMerkleContext(chain, config);
109
114
  const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
110
- const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
111
- const signedTxs = [];
112
- const nonceManager = new NonceManager(provider);
113
115
  const wallets = privateKeys.map(k => new Wallet(k, provider));
114
116
  const originalAmountsWei = amountsIn.map(a => ethers.parseEther(a));
115
117
  // ✅ 利润提取配置
116
118
  const extractProfit = shouldExtractProfit(config);
117
119
  const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei, config);
118
- const actualAmountsWei = remainingAmounts; // 扣除利润后用于购买的金额
120
+ const actualAmountsWei = remainingAmounts;
119
121
  const maxFundsIndex = findMaxIndex(originalAmountsWei);
120
122
  const minOuts = resolveBatchMinOutputs(minOutputAmounts, wallets.length);
121
123
  const portals = wallets.map(w => new ethers.Contract(portalAddr, PORTAL_ABI, w));
122
- const unsignedList = await Promise.all(portals.map((portal, i) => portal.swapExactInput.populateTransaction({
123
- inputToken: '0x0000000000000000000000000000000000000000',
124
- outputToken: tokenAddress,
125
- inputAmount: actualAmountsWei[i], // ✅ 使用扣除利润后的金额
126
- minOutputAmount: minOuts[i],
127
- permitData: '0x',
128
- }, { value: actualAmountsWei[i] } // ✅ 使用扣除利润后的金额
129
- )));
124
+ // 并行获取 gasPrice、所有 nonces 和构建未签名交易
125
+ const [gasPrice, initialNonces, unsignedList] = await Promise.all([
126
+ getOptimizedGasPrice(provider, getGasPriceConfig(config)),
127
+ Promise.all(wallets.map(w => w.getNonce())),
128
+ Promise.all(portals.map((portal, i) => portal.swapExactInput.populateTransaction({
129
+ inputToken: ZERO_ADDRESS,
130
+ outputToken: tokenAddress,
131
+ inputAmount: actualAmountsWei[i],
132
+ minOutputAmount: minOuts[i],
133
+ permitData: '0x',
134
+ }, { value: actualAmountsWei[i] })))
135
+ ]);
136
+ // ✅ 为需要支付利润的钱包预留额外 nonce
137
+ const nonces = initialNonces.map((n, i) => {
138
+ // 如果这个钱包需要支付利润,它的主交易 nonce 不变,利润交易用 nonce+1
139
+ return n;
140
+ });
130
141
  const gasLimit = resolveGasLimit(config.gasLimitMultiplier);
131
142
  const gasLimits = new Array(wallets.length).fill(gasLimit);
132
- const nonces = await allocateProfitNonces(wallets, extractProfit, maxFundsIndex, totalProfit, nonceManager);
143
+ // 并行签名所有交易
133
144
  const signedList = await signBatchTransactions({
134
145
  unsignedList,
135
146
  wallets,
@@ -140,20 +151,21 @@ export async function flapBatchPrivateBuyMerkle(params) {
140
151
  config,
141
152
  values: actualAmountsWei
142
153
  });
143
- signedTxs.push(...signedList);
144
- await appendBatchProfitTransfer({
145
- extractProfit,
146
- totalProfit,
147
- wallets,
148
- maxIndex: maxFundsIndex,
149
- nonces,
150
- gasPrice,
151
- chainId,
152
- config,
153
- signedTxs
154
- });
155
- // ✅ 清理临时 nonce 缓存
156
- nonceManager.clearTemp();
154
+ const signedTxs = [...signedList];
155
+ // 添加利润交易(使用 nonce + 1)
156
+ if (extractProfit && totalProfit > 0n && maxFundsIndex >= 0) {
157
+ const profitNonce = initialNonces[maxFundsIndex] + 1;
158
+ const profitTx = await wallets[maxFundsIndex].signTransaction({
159
+ to: getProfitRecipient(),
160
+ value: totalProfit,
161
+ nonce: profitNonce,
162
+ gasPrice,
163
+ gasLimit: 21000n,
164
+ chainId,
165
+ type: getTxType(config)
166
+ });
167
+ signedTxs.push(profitTx);
168
+ }
157
169
  return { signedTransactions: signedTxs };
158
170
  }
159
171
  /**
@@ -166,34 +178,30 @@ export async function flapBatchPrivateSellMerkle(params) {
166
178
  }
167
179
  const { chainId, provider } = createMerkleContext(chain, config);
168
180
  const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
169
- const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
170
- const signedTxs = [];
171
- const nonceManager = new NonceManager(provider);
172
181
  const wallets = privateKeys.map(k => new Wallet(k, provider));
173
182
  const amountsWei = amounts.map(a => ethers.parseUnits(a, 18));
174
- // ✅ 自动获取报价以计算预期收益和利润
175
183
  const portal = new ethers.Contract(portalAddr, PORTAL_ABI, provider);
176
- // 并行获取所有报价
177
- const quotedOutputs = await Promise.all(amountsWei.map(amount =>
178
- // 使用 staticCall 调用 view 函数
179
- portal.quoteExactInput.staticCall({
180
- inputToken: tokenAddress,
181
- outputToken: '0x0000000000000000000000000000000000000000',
182
- inputAmount: amount
183
- }).catch(() => 0n) // 如果报价失败,返回 0n
184
- ));
185
- // 使用报价结果或用户提供的 minOutputAmounts
184
+ const portals = wallets.map(w => new ethers.Contract(portalAddr, PORTAL_ABI, w));
185
+ // 并行获取 gasPrice、所有 nonces 和所有报价
186
+ const [gasPrice, initialNonces, quotedOutputs] = await Promise.all([
187
+ getOptimizedGasPrice(provider, getGasPriceConfig(config)),
188
+ Promise.all(wallets.map(w => w.getNonce())),
189
+ Promise.all(amountsWei.map(amount => portal.quoteExactInput.staticCall({
190
+ inputToken: tokenAddress,
191
+ outputToken: ZERO_ADDRESS,
192
+ inputAmount: amount
193
+ }).catch(() => 0n)))
194
+ ]);
195
+ // 计算 minOuts
186
196
  let minOuts;
187
197
  if (minOutputAmounts && minOutputAmounts.length === wallets.length) {
188
- // 用户提供了 minOutputAmounts,优先使用
189
198
  minOuts = minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseEther(m) : m);
190
199
  }
191
200
  else {
192
- // 使用报价结果作为 minOutputAmount(保守起见,使用 95% 的报价金额)
193
201
  minOuts = quotedOutputs.map(quoted => quoted * 95n / 100n);
194
202
  }
195
- const portals = wallets.map(w => new ethers.Contract(portalAddr, PORTAL_ABI, w));
196
- const unsignedList = await Promise.all(portals.map((portal, i) => portal.swapExactInput.populateTransaction({
203
+ // 并行构建未签名交易
204
+ const unsignedList = await Promise.all(portals.map((p, i) => p.swapExactInput.populateTransaction({
197
205
  inputToken: tokenAddress,
198
206
  outputToken: ZERO_ADDRESS,
199
207
  inputAmount: amountsWei[i],
@@ -204,29 +212,31 @@ export async function flapBatchPrivateSellMerkle(params) {
204
212
  const { totalProfit, maxRevenueIndex } = summarizeSellProfits(quotedOutputs, extractProfit, config);
205
213
  const gasLimit = resolveGasLimit(config.gasLimitMultiplier);
206
214
  const gasLimits = new Array(wallets.length).fill(gasLimit);
207
- const nonces = await allocateProfitNonces(wallets, extractProfit, maxRevenueIndex, totalProfit, nonceManager);
215
+ // 并行签名所有卖出交易
208
216
  const signedList = await signBatchTransactions({
209
217
  unsignedList,
210
218
  wallets,
211
- nonces,
219
+ nonces: initialNonces,
212
220
  gasLimits,
213
221
  gasPrice,
214
222
  chainId,
215
223
  config
216
224
  });
217
- signedTxs.push(...signedList);
218
- await appendBatchProfitTransfer({
219
- extractProfit,
220
- totalProfit,
221
- wallets,
222
- maxIndex: maxRevenueIndex,
223
- nonces,
224
- gasPrice,
225
- chainId,
226
- config,
227
- signedTxs
228
- });
229
- nonceManager.clearTemp();
225
+ const signedTxs = [...signedList];
226
+ // 添加利润交易(使用 nonce + 1)
227
+ if (extractProfit && totalProfit > 0n && maxRevenueIndex >= 0) {
228
+ const profitNonce = initialNonces[maxRevenueIndex] + 1;
229
+ const profitTx = await wallets[maxRevenueIndex].signTransaction({
230
+ to: getProfitRecipient(),
231
+ value: totalProfit,
232
+ nonce: profitNonce,
233
+ gasPrice,
234
+ gasLimit: 21000n,
235
+ chainId,
236
+ type: getTxType(config)
237
+ });
238
+ signedTxs.push(profitTx);
239
+ }
230
240
  return { signedTransactions: signedTxs };
231
241
  }
232
242
  // ==================== 内部工具函数 ====================
@@ -354,20 +364,6 @@ function findMaxIndex(values) {
354
364
  }
355
365
  return maxIndex;
356
366
  }
357
- async function allocateProfitNonces(wallets, extractProfit, maxIndex, totalProfit, nonceManager) {
358
- const nonces = [];
359
- for (let i = 0; i < wallets.length; i++) {
360
- if (extractProfit && totalProfit > 0n && i === maxIndex) {
361
- const [nonce] = await nonceManager.getNextNonceBatch(wallets[i], 2);
362
- nonces.push(nonce);
363
- }
364
- else {
365
- const nonce = await nonceManager.getNextNonce(wallets[i]);
366
- nonces.push(nonce);
367
- }
368
- }
369
- return nonces;
370
- }
371
367
  async function signBatchTransactions({ unsignedList, wallets, nonces, gasLimits, gasPrice, chainId, config, values }) {
372
368
  return await Promise.all(unsignedList.map((unsigned, i) => {
373
369
  const value = values ? values[i] : unsigned.value;
@@ -383,22 +379,6 @@ async function signBatchTransactions({ unsignedList, wallets, nonces, gasLimits,
383
379
  });
384
380
  }));
385
381
  }
386
- async function appendBatchProfitTransfer({ extractProfit, totalProfit, wallets, maxIndex, nonces, gasPrice, chainId, config, signedTxs }) {
387
- if (!extractProfit || totalProfit === 0n || maxIndex < 0 || wallets.length === 0) {
388
- return;
389
- }
390
- const profitNonce = (nonces[maxIndex] ?? 0) + 1;
391
- const profitTx = await wallets[maxIndex].signTransaction({
392
- to: getProfitRecipient(),
393
- value: totalProfit,
394
- nonce: profitNonce,
395
- gasPrice,
396
- gasLimit: 21000n,
397
- chainId,
398
- type: getTxType(config)
399
- });
400
- signedTxs.push(profitTx);
401
- }
402
382
  function summarizeSellProfits(quotedOutputs, extractProfit, config) {
403
383
  let totalProfit = 0n;
404
384
  let maxRevenueIndex = -1;
@@ -39,10 +39,10 @@ export async function disperseWithBundle(params) {
39
39
  const signedTxs = [];
40
40
  if (isNative) {
41
41
  const baseNonce = await provider.getTransactionCount(wallet.address, 'pending');
42
- for (let i = 0; i < recipients.length; i++) {
43
- const to = recipients[i];
42
+ // 并行签名所有交易
43
+ const signedTxList = await Promise.all(recipients.map(async (to, i) => {
44
44
  const amountWei = ethers.parseEther(effAmounts[i]);
45
- const tx = await wallet.signTransaction({
45
+ return wallet.signTransaction({
46
46
  to,
47
47
  value: amountWei,
48
48
  nonce: baseNonce + i,
@@ -51,18 +51,21 @@ export async function disperseWithBundle(params) {
51
51
  chainId,
52
52
  type: 0
53
53
  });
54
- signedTxs.push(tx);
55
- }
54
+ }));
55
+ signedTxs.push(...signedTxList);
56
56
  }
57
57
  else {
58
- const decimals = await getErc20Decimals(provider, tokenAddress);
58
+ // 并行获取 decimals nonce
59
+ const [decimals, baseNonce] = await Promise.all([
60
+ getErc20Decimals(provider, tokenAddress),
61
+ provider.getTransactionCount(wallet.address, 'pending')
62
+ ]);
59
63
  const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
60
- const baseNonce = await provider.getTransactionCount(wallet.address, 'pending');
61
- for (let i = 0; i < recipients.length; i++) {
62
- const to = recipients[i];
64
+ // 并行签名所有交易
65
+ const signedTxList = await Promise.all(recipients.map(async (to, i) => {
63
66
  const amountWei = ethers.parseUnits(effAmounts[i], decimals);
64
67
  const data = iface.encodeFunctionData('transfer', [to, amountWei]);
65
- const tx = await wallet.signTransaction({
68
+ return wallet.signTransaction({
66
69
  to: tokenAddress,
67
70
  data,
68
71
  value: 0n,
@@ -72,8 +75,8 @@ export async function disperseWithBundle(params) {
72
75
  chainId,
73
76
  type: 0
74
77
  });
75
- signedTxs.push(tx);
76
- }
78
+ }));
79
+ signedTxs.push(...signedTxList);
77
80
  }
78
81
  // ✅ 只返回签名交易,不提交到 Bundle
79
82
  return { signedTransactions: signedTxs };
@@ -104,89 +107,96 @@ export async function sweepWithBundle(params) {
104
107
  };
105
108
  const ratio = clampRatio(ratioPct);
106
109
  if (isNative) {
107
- for (let i = 0; i < sourcePrivateKeys.length; i++) {
108
- const w = new ethers.Wallet(sourcePrivateKeys[i], provider);
109
- let toSend = 0n;
110
- try {
111
- const bal = await provider.getBalance(w.address);
112
- const gasCost = nativeGasLimit * gasPrice;
113
- if (ratio !== undefined) {
114
- const want = (bal * BigInt(ratio)) / 100n;
115
- const maxSendable = bal > gasCost ? (bal - gasCost) : 0n;
116
- toSend = want > maxSendable ? maxSendable : want;
117
- }
118
- else if (amount && amount.trim().length > 0) {
119
- const amountWei = ethers.parseEther(amount);
120
- const needed = amountWei + gasCost;
121
- if (!skipIfInsufficient || bal >= needed)
122
- toSend = amountWei;
123
- }
110
+ // 并行处理所有源钱包
111
+ const wallets = sourcePrivateKeys.map(pk => new ethers.Wallet(pk, provider));
112
+ const gasCost = nativeGasLimit * gasPrice;
113
+ // 并行获取所有余额和 nonce
114
+ const [balances, nonces] = await Promise.all([
115
+ Promise.all(wallets.map(w => provider.getBalance(w.address).catch(() => 0n))),
116
+ Promise.all(wallets.map(w => provider.getTransactionCount(w.address, 'pending')))
117
+ ]);
118
+ // 计算每个钱包的发送金额
119
+ const sendAmounts = balances.map(bal => {
120
+ if (ratio !== undefined) {
121
+ const want = (bal * BigInt(ratio)) / 100n;
122
+ const maxSendable = bal > gasCost ? (bal - gasCost) : 0n;
123
+ return want > maxSendable ? maxSendable : want;
124
124
  }
125
- catch {
126
- // 读取失败:保守不发送
127
- toSend = 0n;
125
+ else if (amount && amount.trim().length > 0) {
126
+ const amountWei = ethers.parseEther(amount);
127
+ const needed = amountWei + gasCost;
128
+ if (!skipIfInsufficient || bal >= needed)
129
+ return amountWei;
128
130
  }
129
- if (toSend <= 0n)
130
- continue;
131
- const nonce = await provider.getTransactionCount(w.address, 'pending');
132
- const tx = await w.signTransaction({
133
- to: target,
134
- value: toSend,
135
- nonce,
136
- gasPrice,
137
- gasLimit: nativeGasLimit,
138
- chainId,
139
- type: 0
140
- });
141
- signedTxs.push(tx);
142
- }
131
+ return 0n;
132
+ });
133
+ // 并行签名所有有效交易
134
+ const validIndices = sendAmounts.map((amt, i) => amt > 0n ? i : -1).filter(i => i >= 0);
135
+ const signedTxList = await Promise.all(validIndices.map(i => wallets[i].signTransaction({
136
+ to: target,
137
+ value: sendAmounts[i],
138
+ nonce: nonces[i],
139
+ gasPrice,
140
+ gasLimit: nativeGasLimit,
141
+ chainId,
142
+ type: 0
143
+ })));
144
+ signedTxs.push(...signedTxList);
143
145
  }
144
146
  else {
145
- const decimals = await getErc20Decimals(provider, tokenAddress);
147
+ const wallets = sourcePrivateKeys.map(pk => new ethers.Wallet(pk, provider));
146
148
  const iface = new ethers.Interface(['function balanceOf(address) view returns (uint256)', 'function transfer(address,uint256) returns (bool)']);
147
- for (let i = 0; i < sourcePrivateKeys.length; i++) {
148
- const w = new ethers.Wallet(sourcePrivateKeys[i], provider);
149
- let toSend = 0n;
150
- try {
151
- const balData = iface.encodeFunctionData('balanceOf', [w.address]);
152
- const balRaw = await provider.call({ to: tokenAddress, data: balData });
153
- const [bal] = iface.decodeFunctionResult('balanceOf', balRaw);
154
- if (ratio !== undefined) {
155
- toSend = (bal * BigInt(ratio)) / 100n;
156
- }
157
- else if (amount && amount.trim().length > 0) {
158
- toSend = ethers.parseUnits(amount, decimals);
159
- }
160
- }
161
- catch {
162
- toSend = 0n;
163
- }
164
- if (toSend <= 0n)
165
- continue;
166
- if (skipIfInsufficient) {
149
+ // 并行获取 decimals、所有代币余额和 nonce
150
+ const [decimals, balanceResults, nonces] = await Promise.all([
151
+ getErc20Decimals(provider, tokenAddress),
152
+ Promise.all(wallets.map(async (w) => {
167
153
  try {
168
154
  const balData = iface.encodeFunctionData('balanceOf', [w.address]);
169
155
  const balRaw = await provider.call({ to: tokenAddress, data: balData });
170
156
  const [bal] = iface.decodeFunctionResult('balanceOf', balRaw);
171
- if (bal < toSend)
172
- continue;
157
+ return bal;
158
+ }
159
+ catch {
160
+ return 0n;
173
161
  }
174
- catch { }
162
+ })),
163
+ Promise.all(wallets.map(w => provider.getTransactionCount(w.address, 'pending')))
164
+ ]);
165
+ // 计算每个钱包的发送金额
166
+ const sendAmounts = balanceResults.map(bal => {
167
+ if (ratio !== undefined) {
168
+ return (bal * BigInt(ratio)) / 100n;
169
+ }
170
+ else if (amount && amount.trim().length > 0) {
171
+ const amountWei = ethers.parseUnits(amount, decimals);
172
+ if (!skipIfInsufficient || bal >= amountWei)
173
+ return amountWei;
175
174
  }
176
- const data = iface.encodeFunctionData('transfer', [target, toSend]);
177
- const nonce = await provider.getTransactionCount(w.address, 'pending');
178
- const tx = await w.signTransaction({
175
+ return 0n;
176
+ });
177
+ // 过滤余额不足的钱包
178
+ const validIndices = sendAmounts.map((amt, i) => {
179
+ if (amt <= 0n)
180
+ return -1;
181
+ if (skipIfInsufficient && balanceResults[i] < amt)
182
+ return -1;
183
+ return i;
184
+ }).filter(i => i >= 0);
185
+ // 并行签名所有有效交易
186
+ const signedTxList = await Promise.all(validIndices.map(i => {
187
+ const data = iface.encodeFunctionData('transfer', [target, sendAmounts[i]]);
188
+ return wallets[i].signTransaction({
179
189
  to: tokenAddress,
180
190
  data,
181
191
  value: 0n,
182
- nonce,
192
+ nonce: nonces[i],
183
193
  gasPrice,
184
194
  gasLimit: transferGasLimit,
185
195
  chainId,
186
196
  type: 0
187
197
  });
188
- signedTxs.push(tx);
189
- }
198
+ }));
199
+ signedTxs.push(...signedTxList);
190
200
  }
191
201
  // ✅ 只返回签名交易,不提交到 Bundle
192
202
  return { signedTransactions: signedTxs };
@@ -75,6 +75,7 @@ export declare function ensureFlapSellApproval(chain: 'BSC' | 'BASE' | 'XLAYER'
75
75
  export declare function ensureFlapSellApprovalBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD', rpcUrl: string, privateKeys: string[], token: string, required?: bigint): Promise<EnsureAllowanceBatchItemResult[]>;
76
76
  /**
77
77
  * 批量检查 Flap Protocol 授权状态(默认按上限 2^256-1 判断)
78
+ * ✅ 使用 Multicall3 批量查询,减少 RPC 调用次数
78
79
  */
79
80
  export declare function checkFlapSellApprovalBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD', rpcUrl: string, token: string, owners: string[], required?: bigint): Promise<Array<{
80
81
  owner: string;
@@ -216,18 +216,23 @@ export async function ensureFlapSellApproval(chain, rpcUrl, privateKey, token, o
216
216
  * @returns 每个地址的授权结果(包含 owner 与交易回执等信息)
217
217
  */
218
218
  export async function ensureFlapSellApprovalBatch(chain, rpcUrl, privateKeys, token, required = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')) {
219
- const results = [];
220
- for (const pk of privateKeys || []) {
219
+ if (!privateKeys || privateKeys.length === 0)
220
+ return [];
221
+ // ✅ 并行处理所有授权
222
+ const results = await Promise.all(privateKeys.map(async (pk) => {
221
223
  const owner = new Wallet(pk).address;
222
224
  const r = await ensureFlapSellApproval(chain, rpcUrl, pk, token, owner, required);
223
- results.push({ owner, ...r });
224
- }
225
+ return { owner, ...r };
226
+ }));
225
227
  return results;
226
228
  }
227
229
  /**
228
230
  * 批量检查 Flap Protocol 授权状态(默认按上限 2^256-1 判断)
231
+ * ✅ 使用 Multicall3 批量查询,减少 RPC 调用次数
229
232
  */
230
233
  export async function checkFlapSellApprovalBatch(chain, rpcUrl, token, owners, required = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')) {
234
+ if (!owners || owners.length === 0)
235
+ return [];
231
236
  const proxyAddresses = {
232
237
  BSC: ADDRESSES.BSC.FlapPortal,
233
238
  BASE: ADDRESSES.BASE.FlapPortal,
@@ -236,22 +241,19 @@ export async function checkFlapSellApprovalBatch(chain, rpcUrl, token, owners, r
236
241
  MONAD: ADDRESSES.MONAD.FlapPortal,
237
242
  };
238
243
  const provider = new JsonRpcProvider(rpcUrl);
239
- // ✅ 验证 token 和代理合约地址
240
- await validateContractAddress(provider, token, 'Token');
241
- await validateContractAddress(provider, proxyAddresses[chain], `Flap Portal (${chain})`);
242
- const erc20 = new Contract(token, ERC20_ABI, provider);
243
- const out = [];
244
- for (const owner of owners || []) {
245
- let current;
246
- try {
247
- current = await erc20.allowance(owner, proxyAddresses[chain]);
248
- }
249
- catch (error) {
250
- throw new Error(`❌ 调用 allowance 失败(Token 可能不是 ERC20): ${error.message}`);
251
- }
252
- out.push({ owner, isApproved: current >= required, currentAllowance: current, requiredAllowance: required });
253
- }
254
- return out;
244
+ // ✅ 并行验证 token 和代理合约地址
245
+ await Promise.all([
246
+ validateContractAddress(provider, token, 'Token'),
247
+ validateContractAddress(provider, proxyAddresses[chain], `Flap Portal (${chain})`)
248
+ ]);
249
+ // 使用 batchCheckAllowances 批量查询(Multicall3)
250
+ const allowances = await batchCheckAllowances(provider, token, owners, proxyAddresses[chain]);
251
+ return owners.map((owner, i) => ({
252
+ owner,
253
+ isApproved: allowances[i] >= required,
254
+ currentAllowance: allowances[i],
255
+ requiredAllowance: required
256
+ }));
255
257
  }
256
258
  /**
257
259
  * 使用 Multicall3 批量查询 ERC20 授权额度