four-flap-meme-sdk 1.2.74 → 1.2.76
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.
|
@@ -2,11 +2,9 @@ import { ethers, Wallet } from 'ethers';
|
|
|
2
2
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
3
3
|
import { ADDRESSES } from '../../utils/constants.js';
|
|
4
4
|
import { FourClient, buildLoginMessage } from '../../clients/four.js';
|
|
5
|
-
import axios from 'axios';
|
|
6
5
|
import { getErrorMessage, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, getProfitRecipient } from './config.js';
|
|
7
6
|
import { batchCheckAllowances } from '../../utils/erc20.js';
|
|
8
7
|
import { trySell } from '../tm.js';
|
|
9
|
-
import TM2Abi from '../../abis/TokenManager2.json' with { type: 'json' };
|
|
10
8
|
const TM2_ABI = [
|
|
11
9
|
'function createToken(bytes args, bytes signature) payable',
|
|
12
10
|
'function buyTokenAMAP(uint256 origin, address token, address to, uint256 funds, uint256 minAmount) payable',
|
|
@@ -141,27 +139,21 @@ export async function createTokenWithBundleBuyMerkle(params) {
|
|
|
141
139
|
}
|
|
142
140
|
nonceManager.clearTemp();
|
|
143
141
|
console.log(`✅ 总共生成了 ${signedTxs.length} 个交易签名 (1创建 + ${buyerKeys.length}买入 + 利润)`);
|
|
144
|
-
// ✅
|
|
145
|
-
console.log('📡
|
|
146
|
-
const club48Endpoint = config.club48Endpoint || 'https://puissant-bsc.48.club';
|
|
142
|
+
// ✅ 使用公共 BSC RPC 提交交易(无 CORS 问题)
|
|
143
|
+
console.log('📡 开始通过公共 RPC 提交交易...');
|
|
147
144
|
const txHashes = [];
|
|
148
|
-
// ✅ 使用
|
|
145
|
+
// ✅ 使用 provider.broadcastTransaction 逐个提交
|
|
149
146
|
for (let i = 0; i < signedTxs.length; i++) {
|
|
150
147
|
try {
|
|
151
148
|
const tx = signedTxs[i];
|
|
152
149
|
console.log(`📤 提交第 ${i + 1}/${signedTxs.length} 个交易...`);
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (response.data.error) {
|
|
160
|
-
throw new Error(`交易 ${i + 1} 提交失败: ${response.data.error.message}`);
|
|
150
|
+
const txResponse = await provider.broadcastTransaction(tx);
|
|
151
|
+
txHashes.push(txResponse.hash);
|
|
152
|
+
console.log(`✅ 交易 ${i + 1} 提交成功: ${txResponse.hash}`);
|
|
153
|
+
// 短暂延迟,避免 nonce 冲突
|
|
154
|
+
if (i < signedTxs.length - 1) {
|
|
155
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
161
156
|
}
|
|
162
|
-
const txHash = response.data.result;
|
|
163
|
-
txHashes.push(txHash);
|
|
164
|
-
console.log(`✅ 交易 ${i + 1} 提交成功: ${txHash}`);
|
|
165
157
|
}
|
|
166
158
|
catch (error) {
|
|
167
159
|
console.error(`❌ 交易 ${i + 1} 提交失败:`, error.message);
|
|
@@ -195,18 +187,25 @@ export async function createTokenWithBundleBuyMerkle(params) {
|
|
|
195
187
|
throw new Error('等待交易确认超时');
|
|
196
188
|
}
|
|
197
189
|
console.log(`✅ 创建交易已确认,区块: ${receipt.blockNumber}`);
|
|
198
|
-
// 解析
|
|
190
|
+
// ✅ 解析 TokenCreate 事件(Four.meme 使用的事件名,注意没有 'd')
|
|
199
191
|
let tokenAddress;
|
|
192
|
+
// 使用 TM2 ABI 解析事件
|
|
193
|
+
const tm2Interface = new ethers.Interface(TM2_ABI);
|
|
200
194
|
for (const log of receipt.logs) {
|
|
201
195
|
try {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
196
|
+
const parsed = tm2Interface.parseLog({
|
|
197
|
+
topics: log.topics,
|
|
198
|
+
data: log.data
|
|
199
|
+
});
|
|
200
|
+
// Four.meme 的事件是 TokenCreate(不是 TokenCreated)
|
|
201
|
+
if (parsed && parsed.name === 'TokenCreate') {
|
|
202
|
+
tokenAddress = parsed.args.token;
|
|
203
|
+
console.log(`✅ 从 TokenCreate 事件解析代币地址: ${tokenAddress}`);
|
|
205
204
|
break;
|
|
206
205
|
}
|
|
207
206
|
}
|
|
208
207
|
catch (e) {
|
|
209
|
-
//
|
|
208
|
+
// 忽略无法解析的日志
|
|
210
209
|
}
|
|
211
210
|
}
|
|
212
211
|
if (!tokenAddress) {
|
|
@@ -220,69 +219,24 @@ export async function createTokenWithBundleBuyMerkle(params) {
|
|
|
220
219
|
txHashes
|
|
221
220
|
};
|
|
222
221
|
}
|
|
223
|
-
/**
|
|
224
|
-
* 等待交易上链并解析代币地址
|
|
225
|
-
*/
|
|
226
|
-
async function waitAndParseTokenAddress(provider, createTxSigned, bundleUuid) {
|
|
227
|
-
console.log('⏳ 等待交易上链,解析代币地址...');
|
|
228
|
-
let tokenAddress = ZERO_ADDRESS;
|
|
229
|
-
const maxAttempts = 30; // 最多等待 30 秒
|
|
230
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
231
|
-
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待 1 秒
|
|
232
|
-
try {
|
|
233
|
-
// 获取创建交易的回执
|
|
234
|
-
const createTxHash = ethers.keccak256(createTxSigned);
|
|
235
|
-
const receipt = await provider.getTransactionReceipt(createTxHash);
|
|
236
|
-
if (receipt && receipt.status === 1) {
|
|
237
|
-
// 从事件日志中解析代币地址
|
|
238
|
-
const tm2Interface = new ethers.Interface(TM2Abi);
|
|
239
|
-
for (const log of receipt.logs) {
|
|
240
|
-
try {
|
|
241
|
-
const parsed = tm2Interface.parseLog({
|
|
242
|
-
topics: log.topics,
|
|
243
|
-
data: log.data
|
|
244
|
-
});
|
|
245
|
-
if (parsed && parsed.name === 'TokenCreate') {
|
|
246
|
-
tokenAddress = parsed.args.token;
|
|
247
|
-
console.log(`✅ 成功解析代币地址: ${tokenAddress}`);
|
|
248
|
-
return tokenAddress;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
catch (e) {
|
|
252
|
-
// 忽略无法解析的日志
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
// 如果没有找到 TokenCreate 事件,尝试 TokenCreated
|
|
256
|
-
const tokenCreatedTopic = ethers.id('TokenCreated(address,address,uint256,uint256,uint256,uint256)');
|
|
257
|
-
for (const log of receipt.logs) {
|
|
258
|
-
if (log.topics[0] === tokenCreatedTopic) {
|
|
259
|
-
tokenAddress = ethers.getAddress('0x' + log.topics[1].slice(26));
|
|
260
|
-
console.log(`✅ 成功解析代币地址: ${tokenAddress}`);
|
|
261
|
-
return tokenAddress;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
// 继续等待
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
if (tokenAddress === ZERO_ADDRESS) {
|
|
272
|
-
console.warn('⚠️ 未能从事件中解析代币地址,返回 0x0');
|
|
273
|
-
}
|
|
274
|
-
return tokenAddress;
|
|
275
|
-
}
|
|
276
222
|
export async function batchBuyWithBundleMerkle(params) {
|
|
277
223
|
const { privateKeys, buyAmounts, tokenAddress, config } = params;
|
|
278
224
|
if (privateKeys.length === 0 || buyAmounts.length !== privateKeys.length) {
|
|
279
225
|
throw new Error(getErrorMessage('KEY_AMOUNT_MISMATCH'));
|
|
280
226
|
}
|
|
281
227
|
const { provider, chainId } = createChainContext(config.rpcUrl);
|
|
282
|
-
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
283
|
-
const nonceManager = new NonceManager(provider);
|
|
284
|
-
const txType = getTxType(config);
|
|
285
228
|
const buyers = createWallets(privateKeys, provider);
|
|
229
|
+
const txType = getTxType(config);
|
|
230
|
+
// ✅ 优化:并行执行 gasPrice 获取和 nonce 预加载
|
|
231
|
+
const [gasPrice, nonceManager] = await Promise.all([
|
|
232
|
+
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
233
|
+
(async () => {
|
|
234
|
+
const nm = new NonceManager(provider);
|
|
235
|
+
// 预加载所有钱包的 nonce(批量查询)
|
|
236
|
+
await Promise.all(buyers.map(w => nm.getNextNonce(w)));
|
|
237
|
+
return nm;
|
|
238
|
+
})()
|
|
239
|
+
]);
|
|
286
240
|
const buyFlow = await executeBuyFlow({
|
|
287
241
|
wallets: buyers,
|
|
288
242
|
buyAmounts,
|
|
@@ -307,10 +261,18 @@ export async function batchSellWithBundleMerkle(params) {
|
|
|
307
261
|
throw new Error(getErrorMessage('SELL_KEY_AMOUNT_MISMATCH'));
|
|
308
262
|
}
|
|
309
263
|
const { provider, chainId } = createChainContext(config.rpcUrl);
|
|
310
|
-
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
311
|
-
const nonceManager = new NonceManager(provider);
|
|
312
264
|
const sellers = createWallets(privateKeys, provider);
|
|
313
265
|
const txType = getTxType(config);
|
|
266
|
+
// ✅ 优化:并行执行 gasPrice 获取和 nonce 预加载
|
|
267
|
+
const [gasPrice, nonceManager] = await Promise.all([
|
|
268
|
+
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
269
|
+
(async () => {
|
|
270
|
+
const nm = new NonceManager(provider);
|
|
271
|
+
// 预加载所有钱包的 nonce(批量查询)
|
|
272
|
+
await Promise.all(sellers.map(w => nm.getNextNonce(w)));
|
|
273
|
+
return nm;
|
|
274
|
+
})()
|
|
275
|
+
]);
|
|
314
276
|
const sellFlow = await executeSellFlow({
|
|
315
277
|
wallets: sellers,
|
|
316
278
|
sellAmounts,
|
|
@@ -128,15 +128,23 @@ export async function batchBuyWithBundleMerkle(params) {
|
|
|
128
128
|
throw new Error(getErrorMessage('KEY_AMOUNT_MISMATCH'));
|
|
129
129
|
}
|
|
130
130
|
const { provider, chainId } = createChainContext(chain, config.rpcUrl);
|
|
131
|
-
const gasPrice = await resolveGasPrice(provider, config);
|
|
132
|
-
const nonceManager = new NonceManager(provider);
|
|
133
|
-
const signedTxs = [];
|
|
134
131
|
const buyers = createWallets(privateKeys, provider);
|
|
135
132
|
const extractProfit = shouldExtractProfit(config);
|
|
136
133
|
const { fundsList, originalAmounts, totalBuyAmount, totalProfit } = analyzeBuyFunds(buyAmounts, config, extractProfit);
|
|
137
134
|
const maxFundsIndex = findMaxIndex(originalAmounts);
|
|
138
|
-
const unsignedBuys = await populateBuyTransactions(buyers, FLAP_PORTAL_ADDRESSES[chain], tokenAddress, fundsList);
|
|
139
135
|
const gasLimits = buildGasLimitList(buyers.length, config);
|
|
136
|
+
const signedTxs = [];
|
|
137
|
+
// ✅ 优化:并行执行所有 RPC 调用(gasPrice + nonces + populateTransaction)
|
|
138
|
+
const [gasPrice, nonceManager, unsignedBuys] = await Promise.all([
|
|
139
|
+
resolveGasPrice(provider, config),
|
|
140
|
+
(async () => {
|
|
141
|
+
const nm = new NonceManager(provider);
|
|
142
|
+
// 预加载所有钱包的 nonce(批量查询)
|
|
143
|
+
await Promise.all(buyers.map(w => nm.getNextNonce(w)));
|
|
144
|
+
return nm;
|
|
145
|
+
})(),
|
|
146
|
+
populateBuyTransactions(buyers, FLAP_PORTAL_ADDRESSES[chain], tokenAddress, fundsList)
|
|
147
|
+
]);
|
|
140
148
|
const buyerNonces = await allocateBuyerNonces(buyers, extractProfit, maxFundsIndex, totalProfit, nonceManager);
|
|
141
149
|
const signedBuys = await signBuyTransactions({
|
|
142
150
|
unsignedBuys,
|
|
@@ -176,25 +184,35 @@ export async function batchSellWithBundleMerkle(params) {
|
|
|
176
184
|
throw new Error(getErrorMessage('SELL_KEY_AMOUNT_MISMATCH'));
|
|
177
185
|
}
|
|
178
186
|
const { provider, chainId } = createChainContext(chain, config.rpcUrl);
|
|
179
|
-
const gasPrice = await resolveGasPrice(provider, config);
|
|
180
|
-
const nonceManager = new NonceManager(provider);
|
|
181
|
-
const signedTxs = [];
|
|
182
187
|
const wallets = createWallets(privateKeys, provider);
|
|
183
188
|
const amountsWei = sellAmounts.map(a => ethers.parseUnits(a, 18));
|
|
184
189
|
const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
|
|
185
190
|
const readOnlyPortal = new ethers.Contract(portalAddr, PORTAL_ABI, provider);
|
|
186
|
-
const
|
|
191
|
+
const gasLimits = buildGasLimitList(wallets.length, config);
|
|
192
|
+
const signedTxs = [];
|
|
193
|
+
// ✅ 优化:并行执行所有 RPC 调用(gasPrice + nonces + quotes)
|
|
194
|
+
const [gasPrice, nonceManager, quotedOutputs] = await Promise.all([
|
|
195
|
+
resolveGasPrice(provider, config),
|
|
196
|
+
(async () => {
|
|
197
|
+
const nm = new NonceManager(provider);
|
|
198
|
+
// 预加载所有钱包的 nonce(批量查询)
|
|
199
|
+
await Promise.all(wallets.map(w => nm.getNextNonce(w)));
|
|
200
|
+
return nm;
|
|
201
|
+
})(),
|
|
202
|
+
quoteSellOutputs(readOnlyPortal, tokenAddress, amountsWei)
|
|
203
|
+
]);
|
|
187
204
|
const minOuts = resolveMinOutputs(minOutputAmounts, wallets.length, quotedOutputs);
|
|
188
|
-
|
|
189
|
-
const unsignedList = await Promise.all(
|
|
205
|
+
// ✅ 优化:使用单个 portal 实例生成所有 populateTransaction(避免重复创建合约实例)
|
|
206
|
+
const unsignedList = await Promise.all(wallets.map((_, i) => readOnlyPortal.swapExactInput.populateTransaction({
|
|
190
207
|
inputToken: tokenAddress,
|
|
191
208
|
outputToken: ZERO_ADDRESS,
|
|
192
209
|
inputAmount: amountsWei[i],
|
|
193
210
|
minOutputAmount: minOuts[i],
|
|
194
211
|
permitData: '0x'
|
|
195
212
|
})));
|
|
196
|
-
|
|
213
|
+
// ✅ 优化:批量获取 nonces(已预加载,直接从缓存读取)
|
|
197
214
|
const nonces = await Promise.all(wallets.map(w => nonceManager.getNextNonce(w)));
|
|
215
|
+
// ✅ 优化:批量签名交易
|
|
198
216
|
const signedList = await Promise.all(unsignedList.map((unsigned, i) => wallets[i].signTransaction({
|
|
199
217
|
...unsigned,
|
|
200
218
|
from: wallets[i].address,
|
|
@@ -270,14 +288,15 @@ function findMaxIndex(values) {
|
|
|
270
288
|
return maxIndex;
|
|
271
289
|
}
|
|
272
290
|
async function populateBuyTransactions(buyers, portalAddr, tokenAddress, fundsList) {
|
|
273
|
-
|
|
274
|
-
|
|
291
|
+
// ✅ 优化:使用单个合约实例(避免重复创建)
|
|
292
|
+
const portal = new ethers.Contract(portalAddr, PORTAL_ABI, buyers[0]);
|
|
293
|
+
return await Promise.all(fundsList.map((funds) => portal.swapExactInput.populateTransaction({
|
|
275
294
|
inputToken: ZERO_ADDRESS,
|
|
276
295
|
outputToken: tokenAddress,
|
|
277
|
-
inputAmount:
|
|
296
|
+
inputAmount: funds,
|
|
278
297
|
minOutputAmount: 0n,
|
|
279
298
|
permitData: '0x'
|
|
280
|
-
}, { value:
|
|
299
|
+
}, { value: funds })));
|
|
281
300
|
}
|
|
282
301
|
function buildGasLimitList(length, config) {
|
|
283
302
|
const gasLimit = getGasLimit(config);
|