four-flap-meme-sdk 1.4.80 → 1.4.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.
@@ -112,8 +112,8 @@ export class BlockRazorClient {
112
112
  */
113
113
  async buildIncentiveTransaction(params) {
114
114
  const { wallet, amount, nonce, gasPrice } = params;
115
- // 获取 nonce
116
- const txNonce = nonce ?? await this.provider.getTransactionCount(wallet.address, 'latest');
115
+ // 获取 nonce(使用 pending 确保包含待处理交易)
116
+ const txNonce = nonce ?? await this.provider.getTransactionCount(wallet.address, 'pending');
117
117
  // 获取 gas price
118
118
  let txGasPrice = gasPrice;
119
119
  if (!txGasPrice) {
@@ -291,8 +291,8 @@ export class BlockRazorClient {
291
291
  * @returns Bundle 结果
292
292
  */
293
293
  async sendBundleWithIncentive(options, incentive) {
294
- // 获取激励钱包的 nonce
295
- const nonce = await this.provider.getTransactionCount(incentive.wallet.address, 'latest');
294
+ // 获取激励钱包的 nonce(使用 pending 确保包含待处理交易)
295
+ const nonce = await this.provider.getTransactionCount(incentive.wallet.address, 'pending');
296
296
  // 构建激励交易(放在 Bundle 最后)
297
297
  const incentiveTx = await this.buildIncentiveTransaction({
298
298
  wallet: incentive.wallet,
@@ -365,10 +365,10 @@ export class BlockRazorClient {
365
365
  if (!transactions || transactions.length === 0) {
366
366
  throw new Error('Transactions array cannot be empty');
367
367
  }
368
- // 获取 nonce
368
+ // 获取 nonce(使用 pending 确保包含待处理交易)
369
369
  let nonce = options?.startNonce;
370
370
  if (nonce === undefined) {
371
- nonce = await this.provider.getTransactionCount(wallet.address, 'latest');
371
+ nonce = await this.provider.getTransactionCount(wallet.address, 'pending');
372
372
  }
373
373
  // 获取 Gas Price(确保不低于最低要求)
374
374
  let gasPrice = options?.gasPrice;
@@ -289,10 +289,10 @@ export class MerkleClient {
289
289
  if (!transactions || transactions.length === 0) {
290
290
  throw new Error('Transactions array cannot be empty');
291
291
  }
292
- // 获取起始 Nonce(使用 latest 避免 pending 状态问题)
292
+ // 获取起始 Nonce(使用 pending 确保包含待处理交易)
293
293
  let nonce = options?.startNonce;
294
294
  if (nonce === undefined) {
295
- nonce = await this.provider.getTransactionCount(wallet.address, 'latest');
295
+ nonce = await this.provider.getTransactionCount(wallet.address, 'pending');
296
296
  }
297
297
  // 获取 Gas Price
298
298
  let gasPrice = options?.gasPrice;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Flap Protocol 发币 + 内盘买到外盘捆绑交易
3
+ *
4
+ * 功能:在一个 Bundle 中完成:
5
+ * 1. 发币(创建新代币)
6
+ * 2. 多个钱包在 Bonding Curve(内盘)买入代币
7
+ * 3. 买到毕业(代币上 DEX)
8
+ * 4. 多个钱包在 PancakeSwap(外盘)继续买入
9
+ */
10
+ import { type FlapSignConfig } from './config.js';
11
+ import type { MerkleSignedResult } from './types.js';
12
+ export type CreateToDexChain = 'bsc';
13
+ export type DexPoolType = 'v2' | 'v3';
14
+ /** 签名配置 */
15
+ export interface CreateToDexSignConfig extends FlapSignConfig {
16
+ reserveGasETH?: number;
17
+ skipQuoteOnError?: boolean;
18
+ }
19
+ /** 代币信息 */
20
+ export interface CreateTokenInfo {
21
+ name: string;
22
+ symbol: string;
23
+ meta: string;
24
+ }
25
+ /** 内盘买入钱包配置 */
26
+ export interface CurveBuyerConfig {
27
+ privateKey: string;
28
+ /** 买入金额,为空则自动分配 */
29
+ buyAmount?: string;
30
+ }
31
+ /** 外盘买入钱包配置 */
32
+ export interface DexBuyerConfig {
33
+ privateKey: string;
34
+ /** 买入金额,为空则自动分配 */
35
+ buyAmount?: string;
36
+ }
37
+ /** 发币 + 一键到外盘参数 */
38
+ export interface FlapCreateToDexParams {
39
+ chain: CreateToDexChain;
40
+ /** Dev 钱包私钥(发币者) */
41
+ devPrivateKey: string;
42
+ /** 代币信息 */
43
+ tokenInfo: CreateTokenInfo;
44
+ /** 预计算的代币地址 */
45
+ tokenAddress: string;
46
+ /** 报价代币(BNB 或 USD1),不传默认 BNB */
47
+ quoteToken?: string;
48
+ /** 报价代币精度,默认 18 */
49
+ quoteTokenDecimals?: number;
50
+ /** 内盘买入钱包 */
51
+ curveBuyers: CurveBuyerConfig[];
52
+ /** 内盘总买入金额(用于自动分配) */
53
+ curveTotalBuyAmount: string;
54
+ /** 是否启用外盘买入 */
55
+ enableDexBuy?: boolean;
56
+ /** 外盘买入钱包(与内盘钱包可相同或不同) */
57
+ dexBuyers?: DexBuyerConfig[];
58
+ /** 外盘总买入金额(用于自动分配) */
59
+ dexTotalBuyAmount?: string;
60
+ /** 外盘池类型(V2 或 V3),默认 V3 */
61
+ dexPoolType?: DexPoolType;
62
+ /** V3 费率(100/500/2500/10000),默认 2500 */
63
+ v3Fee?: number;
64
+ /** 发币扩展参数 */
65
+ dexThresh?: number;
66
+ migratorType?: number;
67
+ taxRate?: number;
68
+ salt?: string;
69
+ /** 配置 */
70
+ config: CreateToDexSignConfig;
71
+ }
72
+ /** 结果类型 */
73
+ export interface FlapCreateToDexResult extends MerkleSignedResult {
74
+ tokenAddress: string;
75
+ metadata?: {
76
+ /** 内盘买入钱包数 */
77
+ curveBuyerCount: number;
78
+ /** 内盘总买入金额 */
79
+ curveTotalBuy: string;
80
+ /** 是否启用外盘买入 */
81
+ enableDexBuy: boolean;
82
+ /** 外盘买入钱包数 */
83
+ dexBuyerCount: number;
84
+ /** 外盘总买入金额 */
85
+ dexTotalBuy: string;
86
+ /** 利润金额 */
87
+ profitAmount?: string;
88
+ };
89
+ }
90
+ /**
91
+ * Flap 发币 + 一键买到外盘捆绑交易
92
+ *
93
+ * 交易顺序:
94
+ * 1. 贿赂交易(BlockRazor)
95
+ * 2. 发币交易(Dev 钱包创建代币)
96
+ * 3. 内盘买入(多个钱包在 Bonding Curve 买入,买到毕业)
97
+ * 4. 外盘买入(多个钱包在 PancakeSwap 买入)
98
+ * 5. 利润多跳转账
99
+ */
100
+ export declare function flapBundleCreateToDex(params: FlapCreateToDexParams): Promise<FlapCreateToDexResult>;
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Flap Protocol 发币 + 内盘买到外盘捆绑交易
3
+ *
4
+ * 功能:在一个 Bundle 中完成:
5
+ * 1. 发币(创建新代币)
6
+ * 2. 多个钱包在 Bonding Curve(内盘)买入代币
7
+ * 3. 买到毕业(代币上 DEX)
8
+ * 4. 多个钱包在 PancakeSwap(外盘)继续买入
9
+ */
10
+ import { ethers, Contract, Wallet } from 'ethers';
11
+ import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../utils/bundle-helpers.js';
12
+ import { FLAP_PORTAL_ADDRESSES, FLAP_ORIGINAL_PORTAL_ADDRESSES } from '../constants.js';
13
+ import { PROFIT_CONFIG, ADDRESSES, ZERO_ADDRESS } from '../../utils/constants.js';
14
+ import { getGasPriceConfig, getTxType, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, PORTAL_ABI } from './config.js';
15
+ // ==================== 链常量 ====================
16
+ const BSC_PANCAKE_V2_ROUTER = ADDRESSES.BSC.PancakeV2Router;
17
+ const BSC_PANCAKE_V3_ROUTER = ADDRESSES.BSC.PancakeV3Router;
18
+ const BSC_WBNB = ADDRESSES.BSC.WBNB;
19
+ // PancakeSwap Router ABI
20
+ const PANCAKE_V3_ROUTER_ABI = [
21
+ {
22
+ "type": "function",
23
+ "name": "exactInputSingle",
24
+ "inputs": [
25
+ {
26
+ "name": "params",
27
+ "type": "tuple",
28
+ "components": [
29
+ { "name": "tokenIn", "type": "address" },
30
+ { "name": "tokenOut", "type": "address" },
31
+ { "name": "fee", "type": "uint24" },
32
+ { "name": "recipient", "type": "address" },
33
+ { "name": "amountIn", "type": "uint256" },
34
+ { "name": "amountOutMinimum", "type": "uint256" },
35
+ { "name": "sqrtPriceLimitX96", "type": "uint160" }
36
+ ]
37
+ }
38
+ ],
39
+ "outputs": [{ "name": "amountOut", "type": "uint256" }],
40
+ "stateMutability": "payable"
41
+ },
42
+ {
43
+ "type": "function",
44
+ "name": "multicall",
45
+ "inputs": [{ "name": "data", "type": "bytes[]" }],
46
+ "outputs": [{ "name": "results", "type": "bytes[]" }],
47
+ "stateMutability": "payable"
48
+ },
49
+ {
50
+ "type": "function",
51
+ "name": "swapExactTokensForTokens",
52
+ "inputs": [
53
+ { "name": "amountIn", "type": "uint256" },
54
+ { "name": "amountOutMin", "type": "uint256" },
55
+ { "name": "path", "type": "address[]" },
56
+ { "name": "to", "type": "address" }
57
+ ],
58
+ "outputs": [{ "name": "amountOut", "type": "uint256" }],
59
+ "stateMutability": "payable"
60
+ }
61
+ ];
62
+ // ==================== 工具函数 ====================
63
+ function getGasLimit(config, defaultGas = 800000) {
64
+ if (config.gasLimit !== undefined) {
65
+ return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
66
+ }
67
+ const multiplier = config.gasLimitMultiplier ?? 1.0;
68
+ return BigInt(Math.ceil(defaultGas * multiplier));
69
+ }
70
+ /** 将金额拆分成多份(权重随机) */
71
+ function splitAmount(totalAmount, count) {
72
+ if (count <= 0)
73
+ throw new Error('拆分份数必须大于 0');
74
+ if (count === 1)
75
+ return [totalAmount];
76
+ const weights = [];
77
+ for (let i = 0; i < count; i++) {
78
+ weights.push(0.5 + Math.random());
79
+ }
80
+ const totalWeight = weights.reduce((a, b) => a + b, 0);
81
+ const amounts = [];
82
+ let allocated = 0n;
83
+ for (let i = 0; i < count - 1; i++) {
84
+ const ratio = weights[i] / totalWeight;
85
+ const amount = BigInt(Math.floor(Number(totalAmount) * ratio));
86
+ amounts.push(amount);
87
+ allocated += amount;
88
+ }
89
+ amounts.push(totalAmount - allocated);
90
+ // 随机打乱
91
+ for (let i = amounts.length - 1; i > 0; i--) {
92
+ const j = Math.floor(Math.random() * (i + 1));
93
+ [amounts[i], amounts[j]] = [amounts[j], amounts[i]];
94
+ }
95
+ return amounts;
96
+ }
97
+ // ==================== 主函数 ====================
98
+ /**
99
+ * Flap 发币 + 一键买到外盘捆绑交易
100
+ *
101
+ * 交易顺序:
102
+ * 1. 贿赂交易(BlockRazor)
103
+ * 2. 发币交易(Dev 钱包创建代币)
104
+ * 3. 内盘买入(多个钱包在 Bonding Curve 买入,买到毕业)
105
+ * 4. 外盘买入(多个钱包在 PancakeSwap 买入)
106
+ * 5. 利润多跳转账
107
+ */
108
+ export async function flapBundleCreateToDex(params) {
109
+ const { chain, devPrivateKey, tokenInfo, tokenAddress, quoteToken, quoteTokenDecimals = 18, curveBuyers, curveTotalBuyAmount, enableDexBuy = false, dexBuyers = [], dexTotalBuyAmount, dexPoolType = 'v3', v3Fee = 2500, config } = params;
110
+ // 验证参数
111
+ if (curveBuyers.length === 0) {
112
+ throw new Error('至少需要一个内盘买入钱包');
113
+ }
114
+ // 判断是否使用原生代币
115
+ const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
116
+ const inputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
117
+ // 创建 Provider
118
+ const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
119
+ chainId: chain === 'bsc' ? 56 : 56,
120
+ name: chain.toUpperCase()
121
+ });
122
+ const portalAddress = FLAP_PORTAL_ADDRESSES[chain.toUpperCase()];
123
+ const originalPortalAddress = FLAP_ORIGINAL_PORTAL_ADDRESSES[chain.toUpperCase()];
124
+ const nonceManager = new NonceManager(provider);
125
+ // 创建钱包实例
126
+ const devWallet = new Wallet(devPrivateKey, provider);
127
+ const curveWallets = curveBuyers.map(b => ({
128
+ wallet: new Wallet(b.privateKey, provider),
129
+ buyAmount: b.buyAmount
130
+ }));
131
+ const dexWallets = (enableDexBuy ? dexBuyers : []).map(b => ({
132
+ wallet: new Wallet(b.privateKey, provider),
133
+ buyAmount: b.buyAmount
134
+ }));
135
+ // ✅ 并行获取 gasPrice + 所有钱包 nonces
136
+ const allWallets = [
137
+ devWallet,
138
+ ...curveWallets.map(c => c.wallet),
139
+ ...dexWallets.map(d => d.wallet)
140
+ ];
141
+ const uniqueWallets = allWallets.filter((w, i) => {
142
+ const addr = w.address.toLowerCase();
143
+ return allWallets.findIndex(x => x.address.toLowerCase() === addr) === i;
144
+ });
145
+ const [gasPrice, noncesArray] = await Promise.all([
146
+ getOptimizedGasPrice(provider, getGasPriceConfig(config)),
147
+ nonceManager.getNextNoncesForWallets(uniqueWallets)
148
+ ]);
149
+ // 构建 nonces Map
150
+ const noncesMap = new Map();
151
+ uniqueWallets.forEach((wallet, i) => {
152
+ noncesMap.set(wallet.address.toLowerCase(), noncesArray[i]);
153
+ });
154
+ const finalGasLimit = getGasLimit(config);
155
+ const txType = getTxType(config);
156
+ const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
157
+ const bribeAmount = getBribeAmount(config);
158
+ const needBribeTx = bribeAmount > 0n;
159
+ // ✅ 计算内盘买入金额
160
+ const curveTotalWei = useNativeToken
161
+ ? ethers.parseEther(curveTotalBuyAmount)
162
+ : ethers.parseUnits(curveTotalBuyAmount, quoteTokenDecimals);
163
+ const curveBuyAmounts = splitAmount(curveTotalWei, curveBuyers.length);
164
+ // ✅ 计算外盘买入金额
165
+ let dexBuyAmounts = [];
166
+ if (enableDexBuy && dexBuyers.length > 0 && dexTotalBuyAmount) {
167
+ const dexTotalWei = useNativeToken
168
+ ? ethers.parseEther(dexTotalBuyAmount)
169
+ : ethers.parseUnits(dexTotalBuyAmount, quoteTokenDecimals);
170
+ dexBuyAmounts = splitAmount(dexTotalWei, dexBuyers.length);
171
+ }
172
+ // ✅ 计算利润
173
+ const totalBuyAmount = curveBuyAmounts.reduce((a, b) => a + b, 0n)
174
+ + dexBuyAmounts.reduce((a, b) => a + b, 0n);
175
+ const profitAmount = (totalBuyAmount * BigInt(PROFIT_CONFIG.RATE_BPS)) / 10000n;
176
+ // ==================== 构建交易 ====================
177
+ const allTransactions = [];
178
+ // 使用第一个内盘买家作为贿赂和利润支付者
179
+ const mainBuyerWallet = curveWallets[0].wallet;
180
+ // 1. 贿赂交易
181
+ if (needBribeTx) {
182
+ const mainAddr = mainBuyerWallet.address.toLowerCase();
183
+ const bribeNonce = noncesMap.get(mainAddr);
184
+ noncesMap.set(mainAddr, bribeNonce + 1);
185
+ const bribeTx = await mainBuyerWallet.signTransaction({
186
+ to: BLOCKRAZOR_BUILDER_EOA,
187
+ value: bribeAmount,
188
+ nonce: bribeNonce,
189
+ gasPrice,
190
+ gasLimit: 21000n,
191
+ chainId: 56,
192
+ type: txType
193
+ });
194
+ allTransactions.push(bribeTx);
195
+ }
196
+ // 2. 发币交易
197
+ {
198
+ const devAddr = devWallet.address.toLowerCase();
199
+ const devNonce = noncesMap.get(devAddr);
200
+ noncesMap.set(devAddr, devNonce + 1);
201
+ const originalPortal = new Contract(originalPortalAddress, PORTAL_ABI, devWallet);
202
+ const createUnsigned = await originalPortal.newTokenV2.populateTransaction({
203
+ name: tokenInfo.name,
204
+ symbol: tokenInfo.symbol,
205
+ meta: tokenInfo.meta,
206
+ dexThresh: (params.dexThresh ?? 1) & 0xff,
207
+ salt: params.salt ?? '0x' + '00'.repeat(32),
208
+ taxRate: (params.taxRate ?? 0) & 0xffff,
209
+ migratorType: (params.migratorType ?? 0) & 0xff,
210
+ quoteToken: inputToken,
211
+ quoteAmt: 0n,
212
+ beneficiary: devWallet.address,
213
+ permitData: '0x'
214
+ });
215
+ const createTx = {
216
+ ...createUnsigned,
217
+ from: devWallet.address,
218
+ nonce: devNonce,
219
+ gasLimit: finalGasLimit,
220
+ chainId: 56,
221
+ type: txType
222
+ };
223
+ if (txType === 2) {
224
+ createTx.maxFeePerGas = gasPrice;
225
+ createTx.maxPriorityFeePerGas = priorityFee;
226
+ }
227
+ else {
228
+ createTx.gasPrice = gasPrice;
229
+ }
230
+ const signedCreate = await devWallet.signTransaction(createTx);
231
+ allTransactions.push(signedCreate);
232
+ }
233
+ // 3. 内盘买入交易(包含毕业触发)
234
+ // 最后一个买入交易会触发毕业,需要更多 gas
235
+ const curveBuyTxPromises = curveWallets.map(async ({ wallet }, i) => {
236
+ const addr = wallet.address.toLowerCase();
237
+ const nonce = noncesMap.get(addr);
238
+ noncesMap.set(addr, nonce + 1);
239
+ const portal = new Contract(portalAddress, PORTAL_ABI, wallet);
240
+ const unsigned = await portal.swapExactInput.populateTransaction({
241
+ inputToken,
242
+ outputToken: tokenAddress,
243
+ inputAmount: curveBuyAmounts[i],
244
+ minOutputAmount: 0n,
245
+ permitData: '0x'
246
+ }, useNativeToken ? { value: curveBuyAmounts[i] } : {});
247
+ // 最后一个买家触发毕业,需要更多 gas
248
+ const isLastBuyer = i === curveWallets.length - 1;
249
+ const buyGasLimit = isLastBuyer ? BigInt(1500000) : finalGasLimit;
250
+ const tx = {
251
+ ...unsigned,
252
+ from: wallet.address,
253
+ nonce,
254
+ gasLimit: buyGasLimit,
255
+ chainId: 56,
256
+ type: txType,
257
+ value: useNativeToken ? curveBuyAmounts[i] : 0n
258
+ };
259
+ if (txType === 2) {
260
+ tx.maxFeePerGas = gasPrice;
261
+ tx.maxPriorityFeePerGas = priorityFee;
262
+ }
263
+ else {
264
+ tx.gasPrice = gasPrice;
265
+ }
266
+ return wallet.signTransaction(tx);
267
+ });
268
+ const signedCurveBuys = await Promise.all(curveBuyTxPromises);
269
+ allTransactions.push(...signedCurveBuys);
270
+ // 4. 外盘买入交易(PancakeSwap)
271
+ if (enableDexBuy && dexWallets.length > 0) {
272
+ const dexBuyTxPromises = dexWallets.map(async ({ wallet }, i) => {
273
+ const addr = wallet.address.toLowerCase();
274
+ const nonce = noncesMap.get(addr);
275
+ noncesMap.set(addr, nonce + 1);
276
+ const buyAmount = dexBuyAmounts[i];
277
+ const smartRouter = dexPoolType === 'v3' ? BSC_PANCAKE_V3_ROUTER : BSC_PANCAKE_V2_ROUTER;
278
+ let calldata;
279
+ let value;
280
+ if (dexPoolType === 'v3') {
281
+ if (useNativeToken) {
282
+ const params = {
283
+ tokenIn: BSC_WBNB,
284
+ tokenOut: tokenAddress,
285
+ fee: v3Fee,
286
+ recipient: wallet.address,
287
+ amountIn: buyAmount,
288
+ amountOutMinimum: 0n,
289
+ sqrtPriceLimitX96: 0n
290
+ };
291
+ const exactInputSingleData = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('exactInputSingle', [params]);
292
+ calldata = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('multicall', [[exactInputSingleData]]);
293
+ value = buyAmount;
294
+ }
295
+ else {
296
+ const params = {
297
+ tokenIn: quoteToken,
298
+ tokenOut: tokenAddress,
299
+ fee: v3Fee,
300
+ recipient: wallet.address,
301
+ amountIn: buyAmount,
302
+ amountOutMinimum: 0n,
303
+ sqrtPriceLimitX96: 0n
304
+ };
305
+ const exactInputSingleData = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('exactInputSingle', [params]);
306
+ calldata = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('multicall', [[exactInputSingleData]]);
307
+ value = 0n;
308
+ }
309
+ }
310
+ else {
311
+ if (useNativeToken) {
312
+ const swapData = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('swapExactTokensForTokens', [buyAmount, 0n, [BSC_WBNB, tokenAddress], wallet.address]);
313
+ calldata = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('multicall', [[swapData]]);
314
+ value = buyAmount;
315
+ }
316
+ else {
317
+ const swapData = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('swapExactTokensForTokens', [buyAmount, 0n, [quoteToken, tokenAddress], wallet.address]);
318
+ calldata = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('multicall', [[swapData]]);
319
+ value = 0n;
320
+ }
321
+ }
322
+ const tx = {
323
+ to: smartRouter,
324
+ data: calldata,
325
+ from: wallet.address,
326
+ nonce,
327
+ gasLimit: BigInt(500000),
328
+ chainId: 56,
329
+ type: txType,
330
+ value
331
+ };
332
+ if (txType === 2) {
333
+ tx.maxFeePerGas = gasPrice;
334
+ tx.maxPriorityFeePerGas = priorityFee;
335
+ }
336
+ else {
337
+ tx.gasPrice = gasPrice;
338
+ }
339
+ return wallet.signTransaction(tx);
340
+ });
341
+ const signedDexBuys = await Promise.all(dexBuyTxPromises);
342
+ allTransactions.push(...signedDexBuys);
343
+ }
344
+ // 5. 利润多跳转账
345
+ let profitHopWallets;
346
+ if (profitAmount > 0n) {
347
+ const mainAddr = mainBuyerWallet.address.toLowerCase();
348
+ const profitNonce = noncesMap.get(mainAddr);
349
+ const profitResult = await buildProfitHopTransactions({
350
+ provider,
351
+ payerWallet: mainBuyerWallet,
352
+ profitAmount,
353
+ profitRecipient: getProfitRecipient(),
354
+ hopCount: PROFIT_HOP_COUNT,
355
+ gasPrice,
356
+ chainId: 56,
357
+ txType,
358
+ startNonce: profitNonce
359
+ });
360
+ allTransactions.push(...profitResult.signedTransactions);
361
+ profitHopWallets = profitResult.hopWallets;
362
+ }
363
+ nonceManager.clearTemp();
364
+ // 返回结果
365
+ return {
366
+ signedTransactions: allTransactions,
367
+ tokenAddress,
368
+ profitHopWallets,
369
+ metadata: {
370
+ curveBuyerCount: curveBuyers.length,
371
+ curveTotalBuy: ethers.formatEther(curveBuyAmounts.reduce((a, b) => a + b, 0n)),
372
+ enableDexBuy,
373
+ dexBuyerCount: dexBuyers.length,
374
+ dexTotalBuy: dexBuyAmounts.length > 0 ? ethers.formatEther(dexBuyAmounts.reduce((a, b) => a + b, 0n)) : '0',
375
+ profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
376
+ }
377
+ };
378
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Flap Protocol 内盘买到外盘捆绑交易
3
+ *
4
+ * 功能:在一个 Bundle 中完成:
5
+ * 1. 多个钱包在 Bonding Curve(内盘)买入代币
6
+ * 2. 最后一个买家触发毕业(代币上 DEX)
7
+ * 3. 多个钱包在 PancakeSwap(外盘)继续买入
8
+ */
9
+ import { FlapSignConfig } from './config.js';
10
+ import type { MerkleSignedResult } from './types.js';
11
+ export type CurveToDexChain = 'bsc';
12
+ export type DexPoolType = 'v2' | 'v3';
13
+ /** 签名配置 */
14
+ export interface CurveToDexSignConfig extends FlapSignConfig {
15
+ reserveGasETH?: number;
16
+ skipQuoteOnError?: boolean;
17
+ }
18
+ /** 内盘买入钱包配置 */
19
+ export interface CurveBuyerConfig {
20
+ privateKey: string;
21
+ /** 买入金额(BNB 或 USD1),为空则自动分配 */
22
+ buyAmount?: string;
23
+ }
24
+ /** 外盘买入钱包配置 */
25
+ export interface DexBuyerConfig {
26
+ privateKey: string;
27
+ /** 买入金额(BNB 或 USD1),为空则自动分配 */
28
+ buyAmount?: string;
29
+ }
30
+ /** 内盘买到外盘参数 */
31
+ export interface FlapCurveToDexParams {
32
+ chain: CurveToDexChain;
33
+ /** 代币地址(已存在的代币) */
34
+ tokenAddress: string;
35
+ /** 报价代币(BNB 或 USD1),不传默认 BNB */
36
+ quoteToken?: string;
37
+ /** 报价代币精度,默认 18 */
38
+ quoteTokenDecimals?: number;
39
+ /** 内盘买入钱包(这些钱包会在 Bonding Curve 上买入) */
40
+ curveBuyers: CurveBuyerConfig[];
41
+ /** 内盘总买入金额(用于自动分配) */
42
+ curveTotalBuyAmount?: string;
43
+ /** 是否触发毕业(上 DEX) */
44
+ triggerGraduate?: boolean;
45
+ /** 毕业触发者(内盘最后一个买家,如果不提供则使用 curveBuyers 的最后一个) */
46
+ graduateWallet?: {
47
+ privateKey: string;
48
+ buyAmount?: string;
49
+ };
50
+ /** 外盘买入钱包(这些钱包会在 PancakeSwap 上买入) */
51
+ dexBuyers?: DexBuyerConfig[];
52
+ /** 外盘总买入金额(用于自动分配) */
53
+ dexTotalBuyAmount?: string;
54
+ /** 外盘池类型(V2 或 V3),默认 V3 */
55
+ dexPoolType?: DexPoolType;
56
+ /** V3 费率(100/500/2500/10000),默认 2500 */
57
+ v3Fee?: number;
58
+ /** 配置 */
59
+ config: CurveToDexSignConfig;
60
+ }
61
+ /** 结果类型 */
62
+ export interface FlapCurveToDexResult extends MerkleSignedResult {
63
+ metadata?: {
64
+ /** 内盘买入钱包数 */
65
+ curveBuyerCount: number;
66
+ /** 内盘总买入金额 */
67
+ curveTotalBuy: string;
68
+ /** 是否触发毕业 */
69
+ graduated: boolean;
70
+ /** 外盘买入钱包数 */
71
+ dexBuyerCount: number;
72
+ /** 外盘总买入金额 */
73
+ dexTotalBuy: string;
74
+ /** 利润金额 */
75
+ profitAmount?: string;
76
+ };
77
+ }
78
+ /**
79
+ * Flap 内盘买到外盘捆绑交易
80
+ *
81
+ * 交易顺序:
82
+ * 1. 贿赂交易(BlockRazor)
83
+ * 2. 内盘买入(多个钱包在 Bonding Curve 买入)
84
+ * 3. 毕业触发(最后一个买家买入触发上 DEX)
85
+ * 4. 外盘买入(多个钱包在 PancakeSwap 买入)
86
+ * 5. 利润多跳转账
87
+ */
88
+ export declare function flapBundleCurveToDex(params: FlapCurveToDexParams): Promise<FlapCurveToDexResult>;
@@ -0,0 +1,424 @@
1
+ /**
2
+ * Flap Protocol 内盘买到外盘捆绑交易
3
+ *
4
+ * 功能:在一个 Bundle 中完成:
5
+ * 1. 多个钱包在 Bonding Curve(内盘)买入代币
6
+ * 2. 最后一个买家触发毕业(代币上 DEX)
7
+ * 3. 多个钱包在 PancakeSwap(外盘)继续买入
8
+ */
9
+ import { ethers, Contract, Wallet } from 'ethers';
10
+ import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../utils/bundle-helpers.js';
11
+ import { FLAP_PORTAL_ADDRESSES } from '../constants.js';
12
+ import { PROFIT_CONFIG, ADDRESSES, ZERO_ADDRESS } from '../../utils/constants.js';
13
+ import { getGasPriceConfig, getTxType, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, PORTAL_ABI } from './config.js';
14
+ // ==================== 链常量 ====================
15
+ const BSC_PANCAKE_V2_ROUTER = ADDRESSES.BSC.PancakeV2Router;
16
+ const BSC_PANCAKE_V3_ROUTER = ADDRESSES.BSC.PancakeV3Router;
17
+ const BSC_WBNB = ADDRESSES.BSC.WBNB;
18
+ // PancakeSwap Router ABI
19
+ const PANCAKE_V3_ROUTER_ABI = [
20
+ {
21
+ "type": "function",
22
+ "name": "exactInputSingle",
23
+ "inputs": [
24
+ {
25
+ "name": "params",
26
+ "type": "tuple",
27
+ "components": [
28
+ { "name": "tokenIn", "type": "address" },
29
+ { "name": "tokenOut", "type": "address" },
30
+ { "name": "fee", "type": "uint24" },
31
+ { "name": "recipient", "type": "address" },
32
+ { "name": "amountIn", "type": "uint256" },
33
+ { "name": "amountOutMinimum", "type": "uint256" },
34
+ { "name": "sqrtPriceLimitX96", "type": "uint160" }
35
+ ]
36
+ }
37
+ ],
38
+ "outputs": [{ "name": "amountOut", "type": "uint256" }],
39
+ "stateMutability": "payable"
40
+ },
41
+ {
42
+ "type": "function",
43
+ "name": "multicall",
44
+ "inputs": [{ "name": "data", "type": "bytes[]" }],
45
+ "outputs": [{ "name": "results", "type": "bytes[]" }],
46
+ "stateMutability": "payable"
47
+ },
48
+ {
49
+ "type": "function",
50
+ "name": "selfPermit",
51
+ "inputs": [
52
+ { "name": "token", "type": "address" },
53
+ { "name": "value", "type": "uint256" },
54
+ { "name": "deadline", "type": "uint256" },
55
+ { "name": "v", "type": "uint8" },
56
+ { "name": "r", "type": "bytes32" },
57
+ { "name": "s", "type": "bytes32" }
58
+ ],
59
+ "outputs": [],
60
+ "stateMutability": "payable"
61
+ },
62
+ {
63
+ "type": "function",
64
+ "name": "swapExactTokensForTokens",
65
+ "inputs": [
66
+ { "name": "amountIn", "type": "uint256" },
67
+ { "name": "amountOutMin", "type": "uint256" },
68
+ { "name": "path", "type": "address[]" },
69
+ { "name": "to", "type": "address" }
70
+ ],
71
+ "outputs": [{ "name": "amountOut", "type": "uint256" }],
72
+ "stateMutability": "payable"
73
+ }
74
+ ];
75
+ // ==================== 工具函数 ====================
76
+ function getGasLimit(config, defaultGas = 800000) {
77
+ if (config.gasLimit !== undefined) {
78
+ return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
79
+ }
80
+ const multiplier = config.gasLimitMultiplier ?? 1.0;
81
+ return BigInt(Math.ceil(defaultGas * multiplier));
82
+ }
83
+ /** 将金额拆分成多份(权重随机) */
84
+ function splitAmount(totalAmount, count) {
85
+ if (count <= 0)
86
+ throw new Error('拆分份数必须大于 0');
87
+ if (count === 1)
88
+ return [totalAmount];
89
+ const weights = [];
90
+ for (let i = 0; i < count; i++) {
91
+ weights.push(0.5 + Math.random());
92
+ }
93
+ const totalWeight = weights.reduce((a, b) => a + b, 0);
94
+ const amounts = [];
95
+ let allocated = 0n;
96
+ for (let i = 0; i < count - 1; i++) {
97
+ const ratio = weights[i] / totalWeight;
98
+ const amount = BigInt(Math.floor(Number(totalAmount) * ratio));
99
+ amounts.push(amount);
100
+ allocated += amount;
101
+ }
102
+ amounts.push(totalAmount - allocated);
103
+ // 随机打乱
104
+ for (let i = amounts.length - 1; i > 0; i--) {
105
+ const j = Math.floor(Math.random() * (i + 1));
106
+ [amounts[i], amounts[j]] = [amounts[j], amounts[i]];
107
+ }
108
+ return amounts;
109
+ }
110
+ // ==================== 主函数 ====================
111
+ /**
112
+ * Flap 内盘买到外盘捆绑交易
113
+ *
114
+ * 交易顺序:
115
+ * 1. 贿赂交易(BlockRazor)
116
+ * 2. 内盘买入(多个钱包在 Bonding Curve 买入)
117
+ * 3. 毕业触发(最后一个买家买入触发上 DEX)
118
+ * 4. 外盘买入(多个钱包在 PancakeSwap 买入)
119
+ * 5. 利润多跳转账
120
+ */
121
+ export async function flapBundleCurveToDex(params) {
122
+ const { chain, tokenAddress, quoteToken, quoteTokenDecimals = 18, curveBuyers, curveTotalBuyAmount, triggerGraduate = true, graduateWallet, dexBuyers = [], dexTotalBuyAmount, dexPoolType = 'v3', v3Fee = 2500, config } = params;
123
+ // 验证参数
124
+ if (curveBuyers.length === 0) {
125
+ throw new Error('至少需要一个内盘买入钱包');
126
+ }
127
+ // 判断是否使用原生代币
128
+ const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
129
+ const inputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
130
+ // 创建 Provider
131
+ const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
132
+ chainId: chain === 'bsc' ? 56 : 56,
133
+ name: chain.toUpperCase()
134
+ });
135
+ const portalAddress = FLAP_PORTAL_ADDRESSES[chain.toUpperCase()];
136
+ const nonceManager = new NonceManager(provider);
137
+ // 创建钱包实例
138
+ const curveWallets = curveBuyers.map(b => ({
139
+ wallet: new Wallet(b.privateKey, provider),
140
+ buyAmount: b.buyAmount
141
+ }));
142
+ const graduateWalletInfo = graduateWallet ? {
143
+ wallet: new Wallet(graduateWallet.privateKey, provider),
144
+ buyAmount: graduateWallet.buyAmount
145
+ } : null;
146
+ const dexWallets = dexBuyers.map(b => ({
147
+ wallet: new Wallet(b.privateKey, provider),
148
+ buyAmount: b.buyAmount
149
+ }));
150
+ // 使用第一个内盘买家作为主钱包(支付贿赂和利润)
151
+ const mainWallet = curveWallets[0].wallet;
152
+ // ✅ 第一批并行:获取 gasPrice + 所有钱包 nonces
153
+ const allWallets = [
154
+ ...curveWallets.map(c => c.wallet),
155
+ ...(graduateWalletInfo ? [graduateWalletInfo.wallet] : []),
156
+ ...dexWallets.map(d => d.wallet)
157
+ ];
158
+ const uniqueWallets = allWallets.filter((w, i) => {
159
+ const addr = w.address.toLowerCase();
160
+ return allWallets.findIndex(x => x.address.toLowerCase() === addr) === i;
161
+ });
162
+ const [gasPrice, noncesArray] = await Promise.all([
163
+ getOptimizedGasPrice(provider, getGasPriceConfig(config)),
164
+ nonceManager.getNextNoncesForWallets(uniqueWallets)
165
+ ]);
166
+ // 构建 nonces Map
167
+ const noncesMap = new Map();
168
+ uniqueWallets.forEach((wallet, i) => {
169
+ noncesMap.set(wallet.address.toLowerCase(), noncesArray[i]);
170
+ });
171
+ const finalGasLimit = getGasLimit(config);
172
+ const txType = getTxType(config);
173
+ const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
174
+ const bribeAmount = getBribeAmount(config);
175
+ const needBribeTx = bribeAmount > 0n;
176
+ // ✅ 计算内盘买入金额
177
+ let curveBuyAmounts;
178
+ if (curveTotalBuyAmount) {
179
+ const total = useNativeToken
180
+ ? ethers.parseEther(curveTotalBuyAmount)
181
+ : ethers.parseUnits(curveTotalBuyAmount, quoteTokenDecimals);
182
+ curveBuyAmounts = splitAmount(total, curveBuyers.length);
183
+ }
184
+ else {
185
+ // 使用各自指定的金额
186
+ curveBuyAmounts = curveBuyers.map(b => {
187
+ if (!b.buyAmount)
188
+ throw new Error('未指定 curveTotalBuyAmount 时,每个钱包必须指定 buyAmount');
189
+ return useNativeToken
190
+ ? ethers.parseEther(b.buyAmount)
191
+ : ethers.parseUnits(b.buyAmount, quoteTokenDecimals);
192
+ });
193
+ }
194
+ // ✅ 计算毕业买入金额
195
+ let graduateBuyAmount = 0n;
196
+ if (triggerGraduate && graduateWalletInfo) {
197
+ if (graduateWalletInfo.buyAmount) {
198
+ graduateBuyAmount = useNativeToken
199
+ ? ethers.parseEther(graduateWalletInfo.buyAmount)
200
+ : ethers.parseUnits(graduateWalletInfo.buyAmount, quoteTokenDecimals);
201
+ }
202
+ else {
203
+ throw new Error('触发毕业时必须指定 graduateWallet.buyAmount');
204
+ }
205
+ }
206
+ // ✅ 计算外盘买入金额
207
+ let dexBuyAmounts = [];
208
+ if (dexBuyers.length > 0) {
209
+ if (dexTotalBuyAmount) {
210
+ const total = useNativeToken
211
+ ? ethers.parseEther(dexTotalBuyAmount)
212
+ : ethers.parseUnits(dexTotalBuyAmount, quoteTokenDecimals);
213
+ dexBuyAmounts = splitAmount(total, dexBuyers.length);
214
+ }
215
+ else {
216
+ dexBuyAmounts = dexBuyers.map(b => {
217
+ if (!b.buyAmount)
218
+ throw new Error('未指定 dexTotalBuyAmount 时,每个钱包必须指定 buyAmount');
219
+ return useNativeToken
220
+ ? ethers.parseEther(b.buyAmount)
221
+ : ethers.parseUnits(b.buyAmount, quoteTokenDecimals);
222
+ });
223
+ }
224
+ }
225
+ // ✅ 计算利润
226
+ const totalBuyAmount = curveBuyAmounts.reduce((a, b) => a + b, 0n)
227
+ + graduateBuyAmount
228
+ + dexBuyAmounts.reduce((a, b) => a + b, 0n);
229
+ const profitAmount = (totalBuyAmount * BigInt(PROFIT_CONFIG.RATE_BPS)) / 10000n;
230
+ // ==================== 构建交易 ====================
231
+ const allTransactions = [];
232
+ // 1. 贿赂交易
233
+ if (needBribeTx) {
234
+ const mainAddr = mainWallet.address.toLowerCase();
235
+ const bribeNonce = noncesMap.get(mainAddr);
236
+ noncesMap.set(mainAddr, bribeNonce + 1);
237
+ const bribeTx = await mainWallet.signTransaction({
238
+ to: BLOCKRAZOR_BUILDER_EOA,
239
+ value: bribeAmount,
240
+ nonce: bribeNonce,
241
+ gasPrice,
242
+ gasLimit: 21000n,
243
+ chainId: 56,
244
+ type: txType
245
+ });
246
+ allTransactions.push(bribeTx);
247
+ }
248
+ // 2. 内盘买入交易
249
+ const curveBuyTxPromises = curveWallets.map(async ({ wallet }, i) => {
250
+ const addr = wallet.address.toLowerCase();
251
+ const nonce = noncesMap.get(addr);
252
+ noncesMap.set(addr, nonce + 1);
253
+ const portal = new Contract(portalAddress, PORTAL_ABI, wallet);
254
+ const unsigned = await portal.swapExactInput.populateTransaction({
255
+ inputToken,
256
+ outputToken: tokenAddress,
257
+ inputAmount: curveBuyAmounts[i],
258
+ minOutputAmount: 0n,
259
+ permitData: '0x'
260
+ }, useNativeToken ? { value: curveBuyAmounts[i] } : {});
261
+ const tx = {
262
+ ...unsigned,
263
+ from: wallet.address,
264
+ nonce,
265
+ gasLimit: finalGasLimit,
266
+ chainId: 56,
267
+ type: txType,
268
+ value: useNativeToken ? curveBuyAmounts[i] : 0n
269
+ };
270
+ if (txType === 2) {
271
+ tx.maxFeePerGas = gasPrice;
272
+ tx.maxPriorityFeePerGas = priorityFee;
273
+ }
274
+ else {
275
+ tx.gasPrice = gasPrice;
276
+ }
277
+ return wallet.signTransaction(tx);
278
+ });
279
+ const signedCurveBuys = await Promise.all(curveBuyTxPromises);
280
+ allTransactions.push(...signedCurveBuys);
281
+ // 3. 毕业触发交易
282
+ if (triggerGraduate && graduateWalletInfo && graduateBuyAmount > 0n) {
283
+ const { wallet } = graduateWalletInfo;
284
+ const addr = wallet.address.toLowerCase();
285
+ const nonce = noncesMap.get(addr);
286
+ noncesMap.set(addr, nonce + 1);
287
+ const portal = new Contract(portalAddress, PORTAL_ABI, wallet);
288
+ const unsigned = await portal.swapExactInput.populateTransaction({
289
+ inputToken,
290
+ outputToken: tokenAddress,
291
+ inputAmount: graduateBuyAmount,
292
+ minOutputAmount: 0n,
293
+ permitData: '0x'
294
+ }, useNativeToken ? { value: graduateBuyAmount } : {});
295
+ const tx = {
296
+ ...unsigned,
297
+ from: wallet.address,
298
+ nonce,
299
+ gasLimit: BigInt(1500000), // 毕业交易需要更多 gas
300
+ chainId: 56,
301
+ type: txType,
302
+ value: useNativeToken ? graduateBuyAmount : 0n
303
+ };
304
+ if (txType === 2) {
305
+ tx.maxFeePerGas = gasPrice;
306
+ tx.maxPriorityFeePerGas = priorityFee;
307
+ }
308
+ else {
309
+ tx.gasPrice = gasPrice;
310
+ }
311
+ const graduateTx = await wallet.signTransaction(tx);
312
+ allTransactions.push(graduateTx);
313
+ }
314
+ // 4. 外盘买入交易(PancakeSwap)
315
+ if (dexWallets.length > 0) {
316
+ const dexBuyTxPromises = dexWallets.map(async ({ wallet }, i) => {
317
+ const addr = wallet.address.toLowerCase();
318
+ const nonce = noncesMap.get(addr);
319
+ noncesMap.set(addr, nonce + 1);
320
+ const buyAmount = dexBuyAmounts[i];
321
+ const smartRouter = dexPoolType === 'v3' ? BSC_PANCAKE_V3_ROUTER : BSC_PANCAKE_V2_ROUTER;
322
+ let calldata;
323
+ let value;
324
+ if (dexPoolType === 'v3') {
325
+ // V3: exactInputSingle
326
+ if (useNativeToken) {
327
+ const params = {
328
+ tokenIn: BSC_WBNB,
329
+ tokenOut: tokenAddress,
330
+ fee: v3Fee,
331
+ recipient: wallet.address,
332
+ amountIn: buyAmount,
333
+ amountOutMinimum: 0n,
334
+ sqrtPriceLimitX96: 0n
335
+ };
336
+ const exactInputSingleData = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('exactInputSingle', [params]);
337
+ calldata = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('multicall', [[exactInputSingleData]]);
338
+ value = buyAmount;
339
+ }
340
+ else {
341
+ // ERC20 需要先 approve,这里简化处理(假设已授权)
342
+ const params = {
343
+ tokenIn: quoteToken,
344
+ tokenOut: tokenAddress,
345
+ fee: v3Fee,
346
+ recipient: wallet.address,
347
+ amountIn: buyAmount,
348
+ amountOutMinimum: 0n,
349
+ sqrtPriceLimitX96: 0n
350
+ };
351
+ const exactInputSingleData = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('exactInputSingle', [params]);
352
+ calldata = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('multicall', [[exactInputSingleData]]);
353
+ value = 0n;
354
+ }
355
+ }
356
+ else {
357
+ // V2: swapExactTokensForTokens
358
+ if (useNativeToken) {
359
+ const swapData = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('swapExactTokensForTokens', [buyAmount, 0n, [BSC_WBNB, tokenAddress], wallet.address]);
360
+ calldata = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('multicall', [[swapData]]);
361
+ value = buyAmount;
362
+ }
363
+ else {
364
+ const swapData = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('swapExactTokensForTokens', [buyAmount, 0n, [quoteToken, tokenAddress], wallet.address]);
365
+ calldata = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('multicall', [[swapData]]);
366
+ value = 0n;
367
+ }
368
+ }
369
+ const tx = {
370
+ to: smartRouter,
371
+ data: calldata,
372
+ from: wallet.address,
373
+ nonce,
374
+ gasLimit: BigInt(500000),
375
+ chainId: 56,
376
+ type: txType,
377
+ value
378
+ };
379
+ if (txType === 2) {
380
+ tx.maxFeePerGas = gasPrice;
381
+ tx.maxPriorityFeePerGas = priorityFee;
382
+ }
383
+ else {
384
+ tx.gasPrice = gasPrice;
385
+ }
386
+ return wallet.signTransaction(tx);
387
+ });
388
+ const signedDexBuys = await Promise.all(dexBuyTxPromises);
389
+ allTransactions.push(...signedDexBuys);
390
+ }
391
+ // 5. 利润多跳转账
392
+ let profitHopWallets;
393
+ if (profitAmount > 0n) {
394
+ const mainAddr = mainWallet.address.toLowerCase();
395
+ const profitNonce = noncesMap.get(mainAddr);
396
+ const profitResult = await buildProfitHopTransactions({
397
+ provider,
398
+ payerWallet: mainWallet,
399
+ profitAmount,
400
+ profitRecipient: getProfitRecipient(),
401
+ hopCount: PROFIT_HOP_COUNT,
402
+ gasPrice,
403
+ chainId: 56,
404
+ txType,
405
+ startNonce: profitNonce
406
+ });
407
+ allTransactions.push(...profitResult.signedTransactions);
408
+ profitHopWallets = profitResult.hopWallets;
409
+ }
410
+ nonceManager.clearTemp();
411
+ // 返回结果
412
+ return {
413
+ signedTransactions: allTransactions,
414
+ profitHopWallets,
415
+ metadata: {
416
+ curveBuyerCount: curveBuyers.length,
417
+ curveTotalBuy: ethers.formatEther(curveBuyAmounts.reduce((a, b) => a + b, 0n)),
418
+ graduated: triggerGraduate,
419
+ dexBuyerCount: dexBuyers.length,
420
+ dexTotalBuy: ethers.formatEther(dexBuyAmounts.reduce((a, b) => a + b, 0n)),
421
+ profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
422
+ }
423
+ };
424
+ }
@@ -9,3 +9,5 @@ export { flapBundleBuyFirstMerkle, type FlapChain, type FlapBuyFirstSignConfig,
9
9
  export { flapPrivateBuyMerkle, flapPrivateSellMerkle, flapBatchPrivateBuyMerkle, flapBatchPrivateSellMerkle } from './private.js';
10
10
  export { pancakeProxyBatchBuyMerkle, pancakeProxyBatchSellMerkle, approvePancakeProxy, approvePancakeProxyBatch } from './pancake-proxy.js';
11
11
  export { flapDisperseWithBundleMerkle, flapSweepWithBundleMerkle, type FlapDisperseSignParams, type FlapDisperseMerkleResult, type FlapSweepSignParams, type FlapSweepMerkleResult } from './utils.js';
12
+ export { flapBundleCurveToDex, type CurveToDexChain, type DexPoolType, type CurveToDexSignConfig, type CurveBuyerConfig, type DexBuyerConfig, type FlapCurveToDexParams, type FlapCurveToDexResult } from './curve-to-dex.js';
13
+ export { flapBundleCreateToDex, type CreateToDexChain, type CreateToDexSignConfig, type CreateTokenInfo, type FlapCreateToDexParams, type FlapCreateToDexResult, type CurveBuyerConfig as CreateCurveBuyerConfig, type DexBuyerConfig as CreateDexBuyerConfig, type DexPoolType as CreateDexPoolType } from './create-to-dex.js';
@@ -15,3 +15,7 @@ export { flapPrivateBuyMerkle, flapPrivateSellMerkle, flapBatchPrivateBuyMerkle,
15
15
  export { pancakeProxyBatchBuyMerkle, pancakeProxyBatchSellMerkle, approvePancakeProxy, approvePancakeProxyBatch } from './pancake-proxy.js';
16
16
  // ✅ 归集和分散方法(支持利润提取、余额最大钱包支付、多跳、ERC20→原生代币报价、Multicall3)
17
17
  export { flapDisperseWithBundleMerkle, flapSweepWithBundleMerkle } from './utils.js';
18
+ // ✅ 内盘买到外盘(Bonding Curve → DEX)- 已存在代币
19
+ export { flapBundleCurveToDex } from './curve-to-dex.js';
20
+ // ✅ 发币 + 一键买到外盘(Create → Curve → DEX)- 新代币
21
+ export { flapBundleCreateToDex } from './create-to-dex.js';
package/dist/index.d.ts CHANGED
@@ -45,6 +45,8 @@ export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, pancakeQuickBatchSwapM
45
45
  export { fourBundleBuyFirstMerkle, type FourBuyFirstConfig, type FourBundleBuyFirstParams, type FourBuyFirstSignConfig, type FourBundleBuyFirstSignParams, type FourBuyFirstResult } from './contracts/tm-bundle-merkle/swap-buy-first.js';
46
46
  export { flapBundleBuyFirstMerkle, type FlapBuyFirstSignConfig, type FlapBuyFirstConfig, type FlapBundleBuyFirstSignParams, type FlapBundleBuyFirstParams, type FlapBuyFirstResult } from './flap/portal-bundle-merkle/swap-buy-first.js';
47
47
  export { pancakeBundleBuyFirstMerkle, type PancakeBuyFirstSignConfig, type PancakeBuyFirstConfig, type PancakeBundleBuyFirstSignParams, type PancakeBundleBuyFirstParams, type PancakeBuyFirstResult } from './pancake/bundle-buy-first.js';
48
+ export { flapBundleCurveToDex, type CurveToDexChain, type DexPoolType, type CurveToDexSignConfig, type CurveBuyerConfig, type DexBuyerConfig, type FlapCurveToDexParams, type FlapCurveToDexResult } from './flap/portal-bundle-merkle/curve-to-dex.js';
49
+ export { flapBundleCreateToDex, type CreateToDexChain, type CreateToDexSignConfig, type CreateTokenInfo, type FlapCreateToDexParams, type FlapCreateToDexResult } from './flap/portal-bundle-merkle/create-to-dex.js';
48
50
  export { PROFIT_CONFIG } from './utils/constants.js';
49
51
  export { quoteV2, quoteV3, quote, getTokenToNativeQuote, getNativeToTokenQuote, getWrappedNativeAddress, getStableCoins, V3_FEE_TIERS, QUOTE_CONFIG, type QuoteParams, type QuoteResult, type SupportedChain, } from './utils/quote-helpers.js';
50
52
  export { submitDirectToRpc, submitDirectToRpcSequential, // ✅ 新增:顺序广播并等待确认(用于多跳)
package/dist/index.js CHANGED
@@ -72,6 +72,10 @@ export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, pancakeQuickBatchSwapM
72
72
  export { fourBundleBuyFirstMerkle } from './contracts/tm-bundle-merkle/swap-buy-first.js';
73
73
  export { flapBundleBuyFirstMerkle } from './flap/portal-bundle-merkle/swap-buy-first.js';
74
74
  export { pancakeBundleBuyFirstMerkle } from './pancake/bundle-buy-first.js';
75
+ // ✅ 内盘买到外盘(Bonding Curve → DEX)- 已存在代币
76
+ export { flapBundleCurveToDex } from './flap/portal-bundle-merkle/curve-to-dex.js';
77
+ // ✅ 发币 + 一键买到外盘(Create → Curve → DEX)- 新代币
78
+ export { flapBundleCreateToDex } from './flap/portal-bundle-merkle/create-to-dex.js';
75
79
  // ✅ 硬编码利润配置(统一管理)
76
80
  export { PROFIT_CONFIG } from './utils/constants.js';
77
81
  // ✅ V2/V3 报价工具(统一管理)
@@ -9,7 +9,7 @@ import { type GeneratedWallet } from './wallet.js';
9
9
  * 用于在 bundle 交易中管理多个钱包的 nonce
10
10
  *
11
11
  * 策略:
12
- * 1. 每次获取 nonce 时查询链上最新状态('latest' 不含 pending)
12
+ * 1. 每次获取 nonce 时查询链上状态(使用 'pending' 包含待处理交易)
13
13
  * 2. 仅在同一批次内维护临时递增缓存
14
14
  * 3. 不持久化缓存,避免因失败交易导致 nonce 过高
15
15
  */
@@ -216,13 +216,17 @@ export interface ProfitHopResult {
216
216
  */
217
217
  export declare const PROFIT_HOP_COUNT = 2;
218
218
  /**
219
- * 生成利润多跳转账交易
219
+ * 生成利润转账交易
220
220
  *
221
+ * BSC 链(chainId=56):使用多跳转账
221
222
  * 流程(2 跳):
222
223
  * 1. 支付者 → 中转1(gas + 利润 + 中转1的gas)
223
224
  * 2. 中转1 → 中转2(gas + 利润)
224
225
  * 3. 中转2 → 利润地址(利润)
225
226
  *
227
+ * 其他链:直接转账到收益地址
228
+ * 流程:支付者 → 利润地址
229
+ *
226
230
  * @param config 配置参数
227
231
  * @returns 签名交易和中转钱包私钥
228
232
  */
@@ -9,7 +9,7 @@ import { generateWallets } from './wallet.js';
9
9
  * 用于在 bundle 交易中管理多个钱包的 nonce
10
10
  *
11
11
  * 策略:
12
- * 1. 每次获取 nonce 时查询链上最新状态('latest' 不含 pending)
12
+ * 1. 每次获取 nonce 时查询链上状态(使用 'pending' 包含待处理交易)
13
13
  * 2. 仅在同一批次内维护临时递增缓存
14
14
  * 3. 不持久化缓存,避免因失败交易导致 nonce 过高
15
15
  */
@@ -33,9 +33,9 @@ export class NonceManager {
33
33
  this.tempNonceCache.set(key, cachedNonce + 1);
34
34
  return cachedNonce;
35
35
  }
36
- // ✅ 使用 'latest' 获取 nonce(已确认的交易)
36
+ // ✅ 使用 'pending' 获取 nonce(包含待处理交易)
37
37
  // 由于前端已移除 nonce 缓存,SDK 每次都从链上获取最新状态
38
- const onchainNonce = await this.provider.getTransactionCount(address, 'latest');
38
+ const onchainNonce = await this.provider.getTransactionCount(address, 'pending');
39
39
  // 缓存下一个值(仅在当前批次内有效)
40
40
  this.tempNonceCache.set(key, onchainNonce + 1);
41
41
  return onchainNonce;
@@ -54,7 +54,7 @@ export class NonceManager {
54
54
  startNonce = this.tempNonceCache.get(key);
55
55
  }
56
56
  else {
57
- startNonce = await this.provider.getTransactionCount(address, 'latest');
57
+ startNonce = await this.provider.getTransactionCount(address, 'pending');
58
58
  }
59
59
  // 更新缓存
60
60
  this.tempNonceCache.set(key, startNonce + count);
@@ -101,9 +101,10 @@ export class NonceManager {
101
101
  let queryResults;
102
102
  try {
103
103
  // ✅ 使用 JSON-RPC 批量请求(单次网络往返)
104
+ // ✅ 使用 'pending' 状态获取 nonce,避免与待处理交易冲突
104
105
  const batchRequests = needQuery.map(({ address }, idx) => ({
105
106
  method: 'eth_getTransactionCount',
106
- params: [address, 'latest'],
107
+ params: [address, 'pending'],
107
108
  id: idx + 1,
108
109
  jsonrpc: '2.0'
109
110
  }));
@@ -119,7 +120,8 @@ export class NonceManager {
119
120
  }
120
121
  catch {
121
122
  // 如果批量请求失败,回退到 Promise.all
122
- const queryPromises = needQuery.map(({ address }) => this.provider.getTransactionCount(address, 'latest'));
123
+ // 同样使用 'pending' 状态
124
+ const queryPromises = needQuery.map(({ address }) => this.provider.getTransactionCount(address, 'pending'));
123
125
  queryResults = await Promise.all(queryPromises);
124
126
  }
125
127
  // 填充结果并更新缓存
@@ -371,13 +373,22 @@ export function decodeV3Path(path) {
371
373
  */
372
374
  export const PROFIT_HOP_COUNT = 2;
373
375
  /**
374
- * 生成利润多跳转账交易
376
+ * ✅ 需要多跳转账的链(目前只有 BSC)
377
+ * 其他链直接转账到收益地址
378
+ */
379
+ const CHAINS_REQUIRE_HOP = [56]; // BSC chainId
380
+ /**
381
+ * 生成利润转账交易
375
382
  *
383
+ * BSC 链(chainId=56):使用多跳转账
376
384
  * 流程(2 跳):
377
385
  * 1. 支付者 → 中转1(gas + 利润 + 中转1的gas)
378
386
  * 2. 中转1 → 中转2(gas + 利润)
379
387
  * 3. 中转2 → 利润地址(利润)
380
388
  *
389
+ * 其他链:直接转账到收益地址
390
+ * 流程:支付者 → 利润地址
391
+ *
381
392
  * @param config 配置参数
382
393
  * @returns 签名交易和中转钱包私钥
383
394
  */
@@ -389,8 +400,33 @@ export async function buildProfitHopTransactions(config) {
389
400
  }
390
401
  // 固定 gas limit(原生代币转账,预留少量缓冲)
391
402
  const nativeTransferGasLimit = 21055n;
403
+ const signedTxs = [];
404
+ const nonceManager = new NonceManager(provider);
405
+ // 获取支付者的 nonce
406
+ const payerNonce = startNonce ?? await nonceManager.getNextNonce(payerWallet);
407
+ // ✅ 判断是否需要多跳转账
408
+ const needHop = CHAINS_REQUIRE_HOP.includes(chainId);
409
+ if (!needHop) {
410
+ // ✅ 非 BSC 链:直接转账到收益地址
411
+ const directTx = await payerWallet.signTransaction({
412
+ to: profitRecipient,
413
+ value: profitAmount,
414
+ nonce: payerNonce,
415
+ gasPrice,
416
+ gasLimit: nativeTransferGasLimit,
417
+ chainId,
418
+ type: txType
419
+ });
420
+ signedTxs.push(directTx);
421
+ return {
422
+ signedTransactions: signedTxs,
423
+ hopWallets: [], // 无中转钱包
424
+ totalNonceUsed: 1
425
+ };
426
+ }
427
+ // ✅ BSC 链:使用多跳转账
392
428
  const gasFeePerHop = nativeTransferGasLimit * gasPrice;
393
- // 生成中转钱包(使用统一的 generateWallets 函数)
429
+ // 生成中转钱包(使用统一的 generateWallets 函数)
394
430
  const hopWalletsInfo = generateWallets(hopCount);
395
431
  const hopWallets = hopWalletsInfo.map(w => new Wallet(w.privateKey, provider));
396
432
  // 计算每个中转钱包需要的金额(从后往前)
@@ -407,10 +443,6 @@ export async function buildProfitHopTransactions(config) {
407
443
  hopAmounts.unshift(hopAmounts[0] + gasFeePerHop);
408
444
  }
409
445
  }
410
- const signedTxs = [];
411
- const nonceManager = new NonceManager(provider);
412
- // 获取支付者的 nonce
413
- const payerNonce = startNonce ?? await nonceManager.getNextNonce(payerWallet);
414
446
  // 1. 支付者 → 第一个中转钱包(包含所有后续的 gas 和利润)
415
447
  const payerTx = await payerWallet.signTransaction({
416
448
  to: hopWallets[0].address,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.80",
3
+ "version": "1.4.83",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",