four-flap-meme-sdk 1.3.81 → 1.3.83
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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/sol/constants.d.ts +136 -0
- package/dist/sol/constants.js +156 -0
- package/dist/sol/dex/index.d.ts +8 -0
- package/dist/sol/dex/index.js +12 -0
- package/dist/sol/dex/meteora/client.d.ts +75 -0
- package/dist/sol/dex/meteora/client.js +218 -0
- package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +61 -0
- package/dist/sol/dex/meteora/damm-v1-bundle.js +112 -0
- package/dist/sol/dex/meteora/damm-v1.d.ts +118 -0
- package/dist/sol/dex/meteora/damm-v1.js +315 -0
- package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +82 -0
- package/dist/sol/dex/meteora/damm-v2-bundle.js +242 -0
- package/dist/sol/dex/meteora/damm-v2.d.ts +172 -0
- package/dist/sol/dex/meteora/damm-v2.js +632 -0
- package/dist/sol/dex/meteora/dbc-bundle.d.ts +123 -0
- package/dist/sol/dex/meteora/dbc-bundle.js +304 -0
- package/dist/sol/dex/meteora/dbc.d.ts +192 -0
- package/dist/sol/dex/meteora/dbc.js +619 -0
- package/dist/sol/dex/meteora/dlmm-bundle.d.ts +39 -0
- package/dist/sol/dex/meteora/dlmm-bundle.js +189 -0
- package/dist/sol/dex/meteora/dlmm.d.ts +146 -0
- package/dist/sol/dex/meteora/dlmm.js +593 -0
- package/dist/sol/dex/meteora/index.d.ts +25 -0
- package/dist/sol/dex/meteora/index.js +65 -0
- package/dist/sol/dex/meteora/types.d.ts +787 -0
- package/dist/sol/dex/meteora/types.js +110 -0
- package/dist/sol/dex/orca/index.d.ts +10 -0
- package/dist/sol/dex/orca/index.js +16 -0
- package/dist/sol/dex/orca/orca-bundle.d.ts +41 -0
- package/dist/sol/dex/orca/orca-bundle.js +140 -0
- package/dist/sol/dex/orca/orca.d.ts +65 -0
- package/dist/sol/dex/orca/orca.js +426 -0
- package/dist/sol/dex/orca/types.d.ts +263 -0
- package/dist/sol/dex/orca/types.js +38 -0
- package/dist/sol/dex/orca/wavebreak-bundle.d.ts +34 -0
- package/dist/sol/dex/orca/wavebreak-bundle.js +189 -0
- package/dist/sol/dex/orca/wavebreak-types.d.ts +227 -0
- package/dist/sol/dex/orca/wavebreak-types.js +23 -0
- package/dist/sol/dex/orca/wavebreak.d.ts +78 -0
- package/dist/sol/dex/orca/wavebreak.js +469 -0
- package/dist/sol/dex/pump/index.d.ts +9 -0
- package/dist/sol/dex/pump/index.js +14 -0
- package/dist/sol/dex/pump/pump-bundle.d.ts +92 -0
- package/dist/sol/dex/pump/pump-bundle.js +383 -0
- package/dist/sol/dex/pump/pump-swap-bundle.d.ts +103 -0
- package/dist/sol/dex/pump/pump-swap-bundle.js +380 -0
- package/dist/sol/dex/pump/pump-swap.d.ts +46 -0
- package/dist/sol/dex/pump/pump-swap.js +199 -0
- package/dist/sol/dex/pump/pump.d.ts +35 -0
- package/dist/sol/dex/pump/pump.js +352 -0
- package/dist/sol/dex/pump/types.d.ts +215 -0
- package/dist/sol/dex/pump/types.js +5 -0
- package/dist/sol/dex/raydium/index.d.ts +8 -0
- package/dist/sol/dex/raydium/index.js +12 -0
- package/dist/sol/dex/raydium/launchlab.d.ts +68 -0
- package/dist/sol/dex/raydium/launchlab.js +210 -0
- package/dist/sol/dex/raydium/raydium-bundle.d.ts +64 -0
- package/dist/sol/dex/raydium/raydium-bundle.js +324 -0
- package/dist/sol/dex/raydium/raydium.d.ts +40 -0
- package/dist/sol/dex/raydium/raydium.js +366 -0
- package/dist/sol/dex/raydium/types.d.ts +240 -0
- package/dist/sol/dex/raydium/types.js +5 -0
- package/dist/sol/index.d.ts +10 -0
- package/dist/sol/index.js +16 -0
- package/dist/sol/jito/bundle.d.ts +90 -0
- package/dist/sol/jito/bundle.js +263 -0
- package/dist/sol/jito/index.d.ts +7 -0
- package/dist/sol/jito/index.js +7 -0
- package/dist/sol/jito/tip.d.ts +51 -0
- package/dist/sol/jito/tip.js +83 -0
- package/dist/sol/jito/types.d.ts +100 -0
- package/dist/sol/jito/types.js +5 -0
- package/dist/sol/token/create-complete.d.ts +115 -0
- package/dist/sol/token/create-complete.js +235 -0
- package/dist/sol/token/create-token.d.ts +57 -0
- package/dist/sol/token/create-token.js +230 -0
- package/dist/sol/token/index.d.ts +9 -0
- package/dist/sol/token/index.js +14 -0
- package/dist/sol/token/metadata-upload.d.ts +86 -0
- package/dist/sol/token/metadata-upload.js +173 -0
- package/dist/sol/token/metadata.d.ts +92 -0
- package/dist/sol/token/metadata.js +274 -0
- package/dist/sol/token/types.d.ts +153 -0
- package/dist/sol/token/types.js +5 -0
- package/dist/sol/types.d.ts +176 -0
- package/dist/sol/types.js +7 -0
- package/dist/sol/utils/balance.d.ts +160 -0
- package/dist/sol/utils/balance.js +638 -0
- package/dist/sol/utils/connection.d.ts +69 -0
- package/dist/sol/utils/connection.js +156 -0
- package/dist/sol/utils/index.d.ts +9 -0
- package/dist/sol/utils/index.js +9 -0
- package/dist/sol/utils/lp-inspect.d.ts +129 -0
- package/dist/sol/utils/lp-inspect.js +515 -0
- package/dist/sol/utils/transfer.d.ts +125 -0
- package/dist/sol/utils/transfer.js +220 -0
- package/dist/sol/utils/wallet.d.ts +107 -0
- package/dist/sol/utils/wallet.js +210 -0
- 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 +23 -3
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +0 -16
- package/dist/flap/portal-bundle-merkle/encryption.js +0 -146
|
@@ -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 授权额度
|
package/dist/utils/lp-inspect.js
CHANGED
|
@@ -103,18 +103,15 @@ export async function getFactoryFromRouter(routerAddress, rpcUrl, routerType = '
|
|
|
103
103
|
else {
|
|
104
104
|
// V3 SwapRouter02 可能同时有 V2 和 V3 Factory
|
|
105
105
|
const router = new Contract(routerAddress, V3_ROUTER_ABI, provider);
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
result.v2Factory =
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
}
|
|
106
|
+
// ✅ 并行获取 V3 和 V2 Factory
|
|
107
|
+
const [v3Factory, v2Factory] = await Promise.all([
|
|
108
|
+
router.factory().catch(() => undefined),
|
|
109
|
+
router.factoryV2().catch(() => undefined)
|
|
110
|
+
]);
|
|
111
|
+
if (v3Factory)
|
|
112
|
+
result.v3Factory = v3Factory;
|
|
113
|
+
if (v2Factory)
|
|
114
|
+
result.v2Factory = v2Factory;
|
|
118
115
|
}
|
|
119
116
|
}
|
|
120
117
|
catch (error) {
|
|
@@ -590,35 +587,42 @@ async function queryV2Pairs(token, factoryAddress, quoteTokens, provider, multic
|
|
|
590
587
|
return results;
|
|
591
588
|
}
|
|
592
589
|
async function queryV2PairsFallback(token, factoryAddress, quoteTokens, provider, tokenDecimals, shouldFormat) {
|
|
593
|
-
const results = [];
|
|
594
590
|
const factory = new Contract(factoryAddress, I_UNIV2_FACTORY_ABI, provider);
|
|
595
|
-
|
|
591
|
+
// ✅ 并行查询所有 quoteToken 的交易对
|
|
592
|
+
const pairResults = await Promise.all(quoteTokens.map(async (qt) => {
|
|
596
593
|
try {
|
|
597
594
|
const pairAddress = await factory.getPair(token, qt.address);
|
|
598
|
-
if (pairAddress
|
|
599
|
-
|
|
600
|
-
const [token0, token1] = await Promise.all([pair.token0(), pair.token1()]);
|
|
601
|
-
const [r0, r1] = await pair.getReserves();
|
|
602
|
-
const isToken0 = token0.toLowerCase() === token.toLowerCase();
|
|
603
|
-
const reserveToken = isToken0 ? r0 : r1;
|
|
604
|
-
const reserveQuote = isToken0 ? r1 : r0;
|
|
605
|
-
results.push({
|
|
606
|
-
quoteToken: qt.address,
|
|
607
|
-
quoteSymbol: qt.symbol,
|
|
608
|
-
quoteDecimals: qt.decimals,
|
|
609
|
-
pairAddress,
|
|
610
|
-
reserveToken: formatBalance(reserveToken, shouldFormat, tokenDecimals),
|
|
611
|
-
reserveQuote: formatBalance(reserveQuote, shouldFormat, qt.decimals),
|
|
612
|
-
reserveQuoteRaw: reserveQuote,
|
|
613
|
-
reserveTokenRaw: reserveToken, // ✅ 新增
|
|
614
|
-
});
|
|
595
|
+
if (!pairAddress || pairAddress.toLowerCase() === ZERO_ADDRESS) {
|
|
596
|
+
return null;
|
|
615
597
|
}
|
|
598
|
+
const pair = new Contract(pairAddress, I_UNIV2_PAIR_ABI, provider);
|
|
599
|
+
// ✅ 并行获取 token0、token1 和 reserves
|
|
600
|
+
const [token0, token1, reserves] = await Promise.all([
|
|
601
|
+
pair.token0(),
|
|
602
|
+
pair.token1(),
|
|
603
|
+
pair.getReserves()
|
|
604
|
+
]);
|
|
605
|
+
const [r0, r1] = reserves;
|
|
606
|
+
const isToken0 = token0.toLowerCase() === token.toLowerCase();
|
|
607
|
+
const reserveToken = isToken0 ? r0 : r1;
|
|
608
|
+
const reserveQuote = isToken0 ? r1 : r0;
|
|
609
|
+
return {
|
|
610
|
+
quoteToken: qt.address,
|
|
611
|
+
quoteSymbol: qt.symbol,
|
|
612
|
+
quoteDecimals: qt.decimals,
|
|
613
|
+
pairAddress,
|
|
614
|
+
reserveToken: formatBalance(reserveToken, shouldFormat, tokenDecimals),
|
|
615
|
+
reserveQuote: formatBalance(reserveQuote, shouldFormat, qt.decimals),
|
|
616
|
+
reserveQuoteRaw: reserveQuote,
|
|
617
|
+
reserveTokenRaw: reserveToken,
|
|
618
|
+
};
|
|
616
619
|
}
|
|
617
620
|
catch {
|
|
618
|
-
|
|
621
|
+
return null;
|
|
619
622
|
}
|
|
620
|
-
}
|
|
621
|
-
|
|
623
|
+
}));
|
|
624
|
+
// 过滤掉 null 结果
|
|
625
|
+
return pairResults.filter((r) => r !== null);
|
|
622
626
|
}
|
|
623
627
|
// ============================================================================
|
|
624
628
|
// V3 批量查询
|
|
@@ -715,43 +719,51 @@ async function queryV3Pools(token, factoryAddress, quoteTokens, feeTiers, provid
|
|
|
715
719
|
return results;
|
|
716
720
|
}
|
|
717
721
|
async function queryV3PoolsFallback(token, factoryAddress, quoteTokens, feeTiers, provider, tokenDecimals, shouldFormat) {
|
|
718
|
-
const results = [];
|
|
719
722
|
const factory = new Contract(factoryAddress, I_UNIV3_FACTORY_ABI, provider);
|
|
720
723
|
const tokenContract = new Contract(token, I_ERC20_ABI, provider);
|
|
724
|
+
// ✅ 构建所有查询组合
|
|
725
|
+
const queries = [];
|
|
721
726
|
for (const qt of quoteTokens) {
|
|
722
|
-
const
|
|
727
|
+
const tokenLower = token.toLowerCase();
|
|
728
|
+
const quoteLower = qt.address.toLowerCase();
|
|
729
|
+
const [token0, token1] = tokenLower < quoteLower
|
|
730
|
+
? [tokenLower, quoteLower]
|
|
731
|
+
: [quoteLower, tokenLower];
|
|
723
732
|
for (const fee of feeTiers) {
|
|
724
|
-
|
|
725
|
-
const tokenLower = token.toLowerCase();
|
|
726
|
-
const quoteLower = qt.address.toLowerCase();
|
|
727
|
-
const [token0, token1] = tokenLower < quoteLower
|
|
728
|
-
? [tokenLower, quoteLower]
|
|
729
|
-
: [quoteLower, tokenLower];
|
|
730
|
-
const poolAddress = await factory.getPool(token0, token1, fee);
|
|
731
|
-
if (poolAddress && poolAddress.toLowerCase() !== ZERO_ADDRESS) {
|
|
732
|
-
const [tokenBalance, quoteBalance] = await Promise.all([
|
|
733
|
-
tokenContract.balanceOf(poolAddress),
|
|
734
|
-
quoteContract.balanceOf(poolAddress),
|
|
735
|
-
]);
|
|
736
|
-
results.push({
|
|
737
|
-
quoteToken: qt.address,
|
|
738
|
-
quoteSymbol: qt.symbol,
|
|
739
|
-
quoteDecimals: qt.decimals,
|
|
740
|
-
pairAddress: poolAddress,
|
|
741
|
-
reserveToken: formatBalance(tokenBalance, shouldFormat, tokenDecimals),
|
|
742
|
-
reserveQuote: formatBalance(quoteBalance, shouldFormat, qt.decimals),
|
|
743
|
-
reserveQuoteRaw: quoteBalance,
|
|
744
|
-
reserveTokenRaw: tokenBalance, // ✅ 新增
|
|
745
|
-
fee,
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
catch {
|
|
750
|
-
// 跳过失败的查询
|
|
751
|
-
}
|
|
733
|
+
queries.push({ qt, fee, token0, token1 });
|
|
752
734
|
}
|
|
753
735
|
}
|
|
754
|
-
|
|
736
|
+
// ✅ 并行查询所有池子
|
|
737
|
+
const poolResults = await Promise.all(queries.map(async ({ qt, fee, token0, token1 }) => {
|
|
738
|
+
try {
|
|
739
|
+
const poolAddress = await factory.getPool(token0, token1, fee);
|
|
740
|
+
if (!poolAddress || poolAddress.toLowerCase() === ZERO_ADDRESS) {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
const quoteContract = new Contract(qt.address, I_ERC20_ABI, provider);
|
|
744
|
+
// ✅ 并行获取余额
|
|
745
|
+
const [tokenBalance, quoteBalance] = await Promise.all([
|
|
746
|
+
tokenContract.balanceOf(poolAddress),
|
|
747
|
+
quoteContract.balanceOf(poolAddress),
|
|
748
|
+
]);
|
|
749
|
+
return {
|
|
750
|
+
quoteToken: qt.address,
|
|
751
|
+
quoteSymbol: qt.symbol,
|
|
752
|
+
quoteDecimals: qt.decimals,
|
|
753
|
+
pairAddress: poolAddress,
|
|
754
|
+
reserveToken: formatBalance(tokenBalance, shouldFormat, tokenDecimals),
|
|
755
|
+
reserveQuote: formatBalance(quoteBalance, shouldFormat, qt.decimals),
|
|
756
|
+
reserveQuoteRaw: quoteBalance,
|
|
757
|
+
reserveTokenRaw: tokenBalance,
|
|
758
|
+
fee,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
}));
|
|
765
|
+
// 过滤掉 null 结果
|
|
766
|
+
return poolResults.filter((r) => r !== null);
|
|
755
767
|
}
|
|
756
768
|
// ============================================================================
|
|
757
769
|
// 平台类型判断
|
|
@@ -52,12 +52,9 @@ async function _stealthTransferInternal(params) {
|
|
|
52
52
|
if (rootBal < needed) {
|
|
53
53
|
throw new Error(`insufficient balance: need ${needed.toString()}, have ${rootBal.toString()}`);
|
|
54
54
|
}
|
|
55
|
-
//
|
|
56
|
-
const
|
|
57
|
-
nonces.
|
|
58
|
-
for (const w of hops) {
|
|
59
|
-
nonces.push(await provider.getTransactionCount(w.address, 'pending'));
|
|
60
|
-
}
|
|
55
|
+
// ✅ 并行预取所有 nonce
|
|
56
|
+
const allAddresses = [rootAddr, ...hops.map(w => w.address)];
|
|
57
|
+
const nonces = await Promise.all(allAddresses.map(addr => provider.getTransactionCount(addr, 'pending')));
|
|
61
58
|
const signed = [];
|
|
62
59
|
const hopAddresses = [];
|
|
63
60
|
if (mode !== 'erc20') {
|
|
@@ -73,14 +70,14 @@ async function _stealthTransferInternal(params) {
|
|
|
73
70
|
type: 0,
|
|
74
71
|
});
|
|
75
72
|
signed.push(fundTx);
|
|
76
|
-
// 2)
|
|
77
|
-
|
|
73
|
+
// 2) ✅ 并行签名所有跳转交易
|
|
74
|
+
const hopTxs = await Promise.all(hops.map(async (hop, i) => {
|
|
78
75
|
const isLast = i === hops.length - 1;
|
|
79
76
|
const nextTo = isLast ? finalTo : hops[i + 1].address;
|
|
80
77
|
const remain = BigInt(hops.length - 1 - i);
|
|
81
78
|
const value = isLast ? amountWei : amountWei + perHopFee * remain;
|
|
82
|
-
hopAddresses.push(
|
|
83
|
-
|
|
79
|
+
hopAddresses.push(hop.address);
|
|
80
|
+
return hop.signTransaction({
|
|
84
81
|
to: nextTo,
|
|
85
82
|
value,
|
|
86
83
|
nonce: nonces[i + 1],
|
|
@@ -89,61 +86,61 @@ async function _stealthTransferInternal(params) {
|
|
|
89
86
|
chainId,
|
|
90
87
|
type: 0,
|
|
91
88
|
});
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
}));
|
|
90
|
+
signed.push(...hopTxs);
|
|
94
91
|
}
|
|
95
92
|
else {
|
|
96
93
|
// ============== ERC20 代币多跳路径 ==============
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
94
|
+
const erc20Iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
95
|
+
const transferGas = erc20?.transferGasLimit ?? 65000n;
|
|
96
|
+
// 填充 hopAddresses
|
|
97
|
+
hops.forEach(hop => hopAddresses.push(hop.address));
|
|
98
|
+
// ✅ 并行签名所有 gas 注资交易(root -> hop[i])
|
|
99
|
+
const fundTxs = await Promise.all(hops.map((hop, i) => root.signTransaction({
|
|
100
|
+
to: hop.address,
|
|
101
|
+
value: transferGas * gasPrice,
|
|
102
|
+
nonce: nonces[0] + i,
|
|
103
|
+
gasPrice,
|
|
104
|
+
gasLimit,
|
|
105
|
+
chainId,
|
|
106
|
+
type: 0,
|
|
107
|
+
})));
|
|
108
|
+
signed.push(...fundTxs);
|
|
112
109
|
// 代币持有者:可选独立私钥,未提供则用 root
|
|
113
110
|
const tokenOwner = (erc20?.holderPrivateKey && erc20.holderPrivateKey.length > 0)
|
|
114
111
|
? new ethers.Wallet(erc20.holderPrivateKey, provider)
|
|
115
112
|
: root;
|
|
116
|
-
// tokenOwner -> hop0: ERC20 transfer 全量 token
|
|
117
|
-
const erc20Iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
113
|
+
// tokenOwner -> hop0: ERC20 transfer 全量 token
|
|
118
114
|
const callDataRoot = erc20Iface.encodeFunctionData('transfer', [hops[0].address, amountWei]);
|
|
115
|
+
const rootTokenNonce = nonces[0] + hops.length;
|
|
119
116
|
const erc20TxRoot = await tokenOwner.signTransaction({
|
|
120
117
|
to: erc20.address,
|
|
121
118
|
data: callDataRoot,
|
|
122
119
|
value: 0n,
|
|
123
|
-
nonce:
|
|
120
|
+
nonce: rootTokenNonce,
|
|
124
121
|
gasPrice,
|
|
125
|
-
gasLimit:
|
|
122
|
+
gasLimit: transferGas,
|
|
126
123
|
chainId,
|
|
127
124
|
type: 0,
|
|
128
125
|
});
|
|
129
126
|
signed.push(erc20TxRoot);
|
|
130
|
-
// hop
|
|
131
|
-
|
|
127
|
+
// ✅ 并行签名所有 hop 的 ERC20 transfer
|
|
128
|
+
const hopTokenTxs = await Promise.all(hops.map((hop, i) => {
|
|
132
129
|
const isLast = i === hops.length - 1;
|
|
133
130
|
const nextTo = isLast ? finalTo : hops[i + 1].address;
|
|
134
131
|
const callData = erc20Iface.encodeFunctionData('transfer', [nextTo, amountWei]);
|
|
135
|
-
|
|
132
|
+
return hop.signTransaction({
|
|
136
133
|
to: erc20.address,
|
|
137
134
|
data: callData,
|
|
138
135
|
value: 0n,
|
|
139
136
|
nonce: nonces[i + 1],
|
|
140
137
|
gasPrice,
|
|
141
|
-
gasLimit:
|
|
138
|
+
gasLimit: transferGas,
|
|
142
139
|
chainId,
|
|
143
140
|
type: 0,
|
|
144
141
|
});
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
}));
|
|
143
|
+
signed.push(...hopTokenTxs);
|
|
147
144
|
}
|
|
148
145
|
// 3) Bundle 提交
|
|
149
146
|
const club48 = new Club48Client({ endpoint: bundleEndpoint });
|
|
@@ -112,20 +112,24 @@ export async function calculateSellAmount(provider, tokenAddress, walletAddress,
|
|
|
112
112
|
if (sellAmount && sellPercentage) {
|
|
113
113
|
throw new Error('sellAmount 和 sellPercentage 不能同时指定');
|
|
114
114
|
}
|
|
115
|
-
|
|
116
|
-
// 精确数量
|
|
115
|
+
// 精确数量:只需要 decimals
|
|
117
116
|
if (sellAmount) {
|
|
117
|
+
const decimals = await getTokenDecimals(provider, tokenAddress);
|
|
118
118
|
return {
|
|
119
119
|
amount: ethers.parseUnits(sellAmount, decimals),
|
|
120
120
|
decimals
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
|
-
//
|
|
123
|
+
// 百分比:需要 decimals 和 balance
|
|
124
124
|
if (sellPercentage !== undefined) {
|
|
125
125
|
if (sellPercentage <= 0 || sellPercentage > 100) {
|
|
126
126
|
throw new Error('sellPercentage 必须在 0-100 之间');
|
|
127
127
|
}
|
|
128
|
-
|
|
128
|
+
// ✅ 并行获取 decimals 和 balance
|
|
129
|
+
const [decimals, balance] = await Promise.all([
|
|
130
|
+
getTokenDecimals(provider, tokenAddress),
|
|
131
|
+
getTokenBalance(provider, tokenAddress, walletAddress)
|
|
132
|
+
]);
|
|
129
133
|
const amount = (balance * BigInt(Math.floor(sellPercentage * 100))) / 10000n;
|
|
130
134
|
return { amount, decimals };
|
|
131
135
|
}
|