four-flap-meme-sdk 1.3.81 → 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.
- package/dist/contracts/tm-bundle.js +224 -113
- package/dist/flap/portal-bundle-merkle/private.js +87 -107
- package/dist/utils/airdrop-sweep.js +86 -76
- package/dist/utils/erc20.d.ts +1 -0
- package/dist/utils/erc20.js +22 -20
- package/dist/utils/lp-inspect.js +77 -65
- package/dist/utils/stealth-transfer.js +34 -37
- package/dist/utils/swap-helpers.js +8 -4
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ethers, Wallet } from 'ethers';
|
|
2
2
|
import { MerkleClient } from '../../clients/merkle.js';
|
|
3
|
-
import {
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
wallets
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
196
|
-
const unsignedList = await Promise.all(portals.map((
|
|
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
|
-
|
|
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
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
wallets
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
43
|
-
|
|
42
|
+
// ✅ 并行签名所有交易
|
|
43
|
+
const signedTxList = await Promise.all(recipients.map(async (to, i) => {
|
|
44
44
|
const amountWei = ethers.parseEther(effAmounts[i]);
|
|
45
|
-
|
|
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
|
-
|
|
55
|
-
|
|
54
|
+
}));
|
|
55
|
+
signedTxs.push(...signedTxList);
|
|
56
56
|
}
|
|
57
57
|
else {
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
172
|
-
|
|
157
|
+
return bal;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return 0n;
|
|
173
161
|
}
|
|
174
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
189
|
-
|
|
198
|
+
}));
|
|
199
|
+
signedTxs.push(...signedTxList);
|
|
190
200
|
}
|
|
191
201
|
// ✅ 只返回签名交易,不提交到 Bundle
|
|
192
202
|
return { signedTransactions: signedTxs };
|
package/dist/utils/erc20.d.ts
CHANGED
|
@@ -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;
|
package/dist/utils/erc20.js
CHANGED
|
@@ -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
|
-
|
|
220
|
-
|
|
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
|
-
|
|
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
|
-
// ✅
|
|
240
|
-
await
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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 授权额度
|