four-flap-meme-sdk 1.2.87 → 1.2.88

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.
@@ -29,6 +29,7 @@ export interface FlapSwapConfig extends CommonBundleConfig {
29
29
  waitTimeoutMs?: number;
30
30
  skipApprovalCheck?: boolean;
31
31
  }
32
+ import { FundRoutingConfig } from './types.js';
32
33
  export interface FlapBundleSwapSignParams {
33
34
  chain: FlapChain;
34
35
  sellerPrivateKey: string;
@@ -37,6 +38,8 @@ export interface FlapBundleSwapSignParams {
37
38
  buyerPrivateKey: string;
38
39
  tokenAddress: string;
39
40
  config: FlapSwapSignConfig;
41
+ /** 资金路由配置 */
42
+ routing?: FundRoutingConfig;
40
43
  }
41
44
  export interface FlapBundleSwapParams {
42
45
  chain: FlapChain;
@@ -57,6 +60,11 @@ export type FlapSwapResult = {
57
60
  buyAmount: string;
58
61
  hasApproval?: boolean;
59
62
  profitAmount?: string;
63
+ routing?: {
64
+ mainAddress: string;
65
+ hopAddresses: string[];
66
+ totalHops: number;
67
+ };
60
68
  };
61
69
  };
62
70
  /**
@@ -56,11 +56,15 @@ function getNativeTokenName(chain) {
56
56
  * Flap内盘捆绑换手
57
57
  */
58
58
  export async function flapBundleSwapMerkle(params) {
59
- const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, config } = params;
59
+ const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, config, routing // ✅ 新增:路由配置
60
+ } = params;
60
61
  const chainContext = createChainContext(chain, config);
61
62
  const seller = new Wallet(sellerPrivateKey, chainContext.provider);
63
+ // 如果有 routing,买家是 routing 链路的终点;否则是普通 buyer
64
+ // 注意:如果是路由模式,买家不需要预先有 BNB,资金从 seller 来
62
65
  const buyer = new Wallet(buyerPrivateKey, chainContext.provider);
63
66
  const { amount: sellAmountWei, decimals } = await calculateSellAmount(chainContext.provider, tokenAddress, seller.address, sellAmount, sellPercentage);
67
+ // ... (省略部分: approval) ...
64
68
  const approvalTx = config.skipApprovalCheck
65
69
  ? null
66
70
  : await buildApprovalTransaction({
@@ -82,14 +86,93 @@ export async function flapBundleSwapMerkle(params) {
82
86
  slippageBps: config.slippageBps,
83
87
  skipQuoteOnError: config.skipQuoteOnError
84
88
  });
85
- const buyerNeed = await calculateBuyerNeed({
86
- buyer,
87
- quotedNative: quote.quotedNative,
88
- reserveGasEth: config.reserveGasETH,
89
- slippageBps: config.slippageBps,
90
- nativeToken: chainContext.nativeToken
91
- });
89
+ const finalGasLimit = getGasLimit(config);
90
+ const gasPrice = await getOptimizedGasPrice(chainContext.provider, getGasPriceConfig(config));
91
+ const txType = getTxType(config);
92
+ const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
92
93
  const profitAmount = calculateProfitAmount(quote.quotedNative);
94
+ const extractProfit = profitAmount > 0n;
95
+ // ✅ 核心逻辑分支:是否有资金路由
96
+ let routeTxs = [];
97
+ let buyerAvailableFunds = 0n;
98
+ let noncePlan;
99
+ let hopInfo = undefined;
100
+ const nonceManager = new NonceManager(chainContext.provider);
101
+ if (routing) {
102
+ // === 路由模式 ===
103
+ // 1. Seller Sell -> BNB
104
+ // 2. Seller Transfer (BNB - Gas - Profit) -> Main Wallet
105
+ // 3. Main -> Hops -> Buyer
106
+ // 4. Buyer Buy
107
+ console.log('🔄 启用资金路由模式...');
108
+ // 计算 Seller 卖出后剩余可转移金额
109
+ const sellGasCost = finalGasLimit * gasPrice; // 卖出交易 Gas
110
+ const transferGasCost = 21000n * gasPrice; // 每一跳 Gas
111
+ // 预估卖出所得 (保守估计,扣除滑点)
112
+ const safeSlippage = Math.max(0, Math.min(5000, config.slippageBps ?? 100));
113
+ const estimatedSellOutput = (quote.quotedNative * BigInt(10000 - safeSlippage)) / 10000n;
114
+ // Seller 需保留的 Gas (卖出 + 转账 + 利润(如有))
115
+ const sellerTxCount = countTruthy([approvalTx, true, true, profitAmount > 0n]); // Approve?, Sell, Transfer, Profit?
116
+ const sellerTotalGas = BigInt(sellerTxCount) * sellGasCost; // 简化估算,都按大 Gas 算比较安全,或者精确计算
117
+ // 实际上 Seller 转账给 Main 的金额 = 卖出所得 - (卖出Gas + 利润)
118
+ // 注意:这里假设 Seller 账户里有额外的 BNB 支付 Gas,或者从卖出所得里扣
119
+ // 为了稳健,我们假设必须从卖出所得里扣除后续步骤的 Gas
120
+ let transferAmount = estimatedSellOutput - profitAmount;
121
+ // 如果 Seller 余额很低,可能需要扣除 Gas。为了简化,假设 Seller 有少量 BNB 启动。
122
+ // 这里我们只扣除利润。
123
+ if (transferAmount <= 0n) {
124
+ throw new Error(`卖出金额不足以支付利润和 Gas: ${ethers.formatEther(estimatedSellOutput)} BNB`);
125
+ }
126
+ // 构建路由链路
127
+ const routeResult = await buildFundingRoute({
128
+ routing,
129
+ initialAmount: transferAmount,
130
+ seller,
131
+ buyerAddress: buyer.address,
132
+ gasPrice,
133
+ chainId: chainContext.chainId,
134
+ txType,
135
+ nonceManager
136
+ });
137
+ routeTxs = routeResult.signedTxs;
138
+ buyerAvailableFunds = routeResult.finalAmount;
139
+ hopInfo = routeResult.hopInfo;
140
+ console.log(`💸 资金路由: 初始 ${ethers.formatEther(transferAmount)} -> 最终 ${ethers.formatEther(buyerAvailableFunds)}`);
141
+ // 重新规划 Nonce (Seller 和 Buyer 的部分已经由 buildFundingRoute 处理或者需要重新对齐)
142
+ // buildFundingRoute 处理了 Main 和 Hops 的 nonce。
143
+ // Seller 的 nonce 需要在这里处理:Approve -> Sell -> Transfer -> Profit
144
+ // Buyer 的 nonce: Buy
145
+ // 由于 buildFundingRoute 内部使用了 seller 签名了一笔转账,我们需要确保 nonce 顺序
146
+ // 实际上 buildFundingRoute 应该只负责 Main -> Buyer,
147
+ // Seller -> Main 的转账应该在这里构建,以便和 Sell 交易排序。
148
+ // 修正策略:
149
+ // routeResult 只包含 Main -> ... -> Buyer 的交易
150
+ // 这里负责:
151
+ // 1. Seller Sell
152
+ // 2. Seller -> Main
153
+ // 3. ...Route Txs...
154
+ // 4. Buyer Buy
155
+ }
156
+ else {
157
+ // === 普通模式 ===
158
+ const buyerNeed = await calculateBuyerNeed({
159
+ buyer,
160
+ quotedNative: quote.quotedNative,
161
+ reserveGasEth: config.reserveGasETH,
162
+ slippageBps: config.slippageBps,
163
+ nativeToken: chainContext.nativeToken
164
+ });
165
+ await validateBalances({
166
+ buyerNeed,
167
+ buyerAddress: buyer.address,
168
+ portalGasCost: finalGasLimit * gasPrice,
169
+ provider: chainContext.provider,
170
+ chainContext,
171
+ seller
172
+ });
173
+ buyerAvailableFunds = buyerNeed.maxBuyerValue;
174
+ }
175
+ // ... (构建交易对象: sellUnsigned, buyUnsigned) ...
93
176
  const sellUnsigned = await portalSeller.swapExactInput.populateTransaction({
94
177
  inputToken: tokenAddress,
95
178
  outputToken: ZERO_ADDRESS,
@@ -100,53 +183,121 @@ export async function flapBundleSwapMerkle(params) {
100
183
  const buyUnsigned = await portalBuyer.swapExactInput.populateTransaction({
101
184
  inputToken: ZERO_ADDRESS,
102
185
  outputToken: tokenAddress,
103
- inputAmount: buyerNeed.maxBuyerValue,
186
+ inputAmount: buyerAvailableFunds, // ✅ 使用计算出的可用资金
104
187
  minOutputAmount: 0,
105
188
  permitData: '0x'
106
- }, { value: buyerNeed.maxBuyerValue });
107
- const finalGasLimit = getGasLimit(config);
108
- const gasPrice = await getOptimizedGasPrice(chainContext.provider, getGasPriceConfig(config));
109
- const txType = getTxType(config);
110
- const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
111
- await validateBalances({
112
- buyerNeed,
113
- buyerAddress: buyer.address,
114
- portalGasCost: finalGasLimit * gasPrice,
115
- provider: chainContext.provider,
116
- chainContext,
117
- seller
118
- });
119
- const nonceManager = new NonceManager(chainContext.provider);
120
- const noncePlan = await planNonces({
121
- seller,
122
- buyer,
123
- approvalExists: !!approvalTx,
124
- extractProfit: profitAmount > 0n,
125
- nonceManager
126
- });
127
- const sellTx = buildTransactionRequest(sellUnsigned, {
128
- from: seller.address,
129
- nonce: noncePlan.sellerNonce,
130
- gasLimit: finalGasLimit,
131
- gasPrice,
132
- priorityFee,
133
- chainId: chainContext.chainId,
134
- txType
135
- });
136
- const buyTx = buildTransactionRequest(buyUnsigned, {
137
- from: buyer.address,
138
- nonce: noncePlan.buyerNonce,
139
- gasLimit: finalGasLimit,
140
- gasPrice,
141
- priorityFee,
142
- chainId: chainContext.chainId,
143
- txType,
144
- value: buyerNeed.maxBuyerValue
145
- });
146
- const [signedSell, signedBuy] = await Promise.all([
147
- seller.signTransaction(sellTx),
148
- buyer.signTransaction(buyTx)
149
- ]);
189
+ }, { value: buyerAvailableFunds });
190
+ // ... (Nonce 规划) ...
191
+ // 如果是路由模式,Nonce 规划比较复杂,需要手动处理
192
+ let signedSell;
193
+ let signedBuy;
194
+ let sellerTransferTx = null;
195
+ if (routing) {
196
+ // 路由模式下的 Nonce 规划
197
+ // Seller: [Approve], Sell, TransferToMain, [Profit]
198
+ const sellerStartNonce = await nonceManager.getNextNonce(seller); // 获取当前 nonce,不做自增,后面手动加
199
+ let currentSellerNonce = sellerStartNonce;
200
+ // Approve (如果在上面已经构建了,nonce 可能是错的,因为上面还没用 nonceManager?)
201
+ // 上面的 buildApprovalTransaction 内部用了 nonceManager.getNextNonce(seller)
202
+ // 这会导致 nonce 错乱。
203
+ // 🛑 必须重构:buildApprovalTransaction 不应该内部签名,应该返回 Request 或者由外部管理 Nonce
204
+ // 为了不破坏现有结构太大,我们假设 buildApprovalTransaction 消耗了一个 Nonce。
205
+ // 如果 approvalTx 存在,nonceManager 已经记录了 seller 用过一个 nonce。
206
+ // Sell
207
+ currentSellerNonce = await nonceManager.getNextNonce(seller);
208
+ const sellTx = buildTransactionRequest(sellUnsigned, {
209
+ from: seller.address,
210
+ nonce: currentSellerNonce,
211
+ gasLimit: finalGasLimit,
212
+ gasPrice,
213
+ priorityFee,
214
+ chainId: chainContext.chainId,
215
+ txType
216
+ });
217
+ signedSell = await seller.signTransaction(sellTx);
218
+ // Transfer to Main
219
+ const transferToMainNonce = await nonceManager.getNextNonce(seller);
220
+ const transferAmount = quote.quotedNative - profitAmount - (21000n * gasPrice); // 简单扣除 Gas
221
+ sellerTransferTx = await seller.signTransaction({
222
+ to: new Wallet(routing.mainPrivateKey).address,
223
+ value: transferAmount, // 这里是个预估值,必须保守
224
+ nonce: transferToMainNonce,
225
+ gasLimit: 21000n,
226
+ gasPrice,
227
+ chainId: chainContext.chainId,
228
+ type: txType
229
+ });
230
+ // Route Txs (Main -> ... -> Buyer)
231
+ // 这里重新调用 buildFundingRoute,它会处理 Main 和 Hops 的 nonce
232
+ const routeRes = await buildFundingRoute({
233
+ routing,
234
+ initialAmount: transferAmount,
235
+ seller, // 这里其实不需要 seller
236
+ buyerAddress: buyer.address,
237
+ gasPrice,
238
+ chainId: chainContext.chainId,
239
+ txType,
240
+ nonceManager
241
+ });
242
+ routeTxs = routeRes.signedTxs;
243
+ // 更新 Buyer 可用资金 (扣除链路 Gas)
244
+ buyerAvailableFunds = routeRes.finalAmount;
245
+ // 重新构建 Buy Tx (因为金额变了)
246
+ const buyUnsignedFinal = await portalBuyer.swapExactInput.populateTransaction({
247
+ inputToken: ZERO_ADDRESS,
248
+ outputToken: tokenAddress,
249
+ inputAmount: buyerAvailableFunds,
250
+ minOutputAmount: 0,
251
+ permitData: '0x'
252
+ }, { value: buyerAvailableFunds });
253
+ const buyerNonce = await nonceManager.getNextNonce(buyer);
254
+ const buyTx = buildTransactionRequest(buyUnsignedFinal, {
255
+ from: buyer.address,
256
+ nonce: buyerNonce,
257
+ gasLimit: finalGasLimit,
258
+ gasPrice,
259
+ priorityFee,
260
+ chainId: chainContext.chainId,
261
+ txType,
262
+ value: buyerAvailableFunds
263
+ });
264
+ signedBuy = await buyer.signTransaction(buyTx);
265
+ noncePlan = { sellerNonce: currentSellerNonce, buyerNonce, profitNonce: extractProfit ? await nonceManager.getNextNonce(seller) : undefined };
266
+ }
267
+ else {
268
+ // 普通模式 (保持原样)
269
+ noncePlan = await planNonces({
270
+ seller,
271
+ buyer,
272
+ approvalExists: !!approvalTx,
273
+ extractProfit: profitAmount > 0n,
274
+ nonceManager
275
+ });
276
+ const sellTx = buildTransactionRequest(sellUnsigned, {
277
+ from: seller.address,
278
+ nonce: noncePlan.sellerNonce,
279
+ gasLimit: finalGasLimit,
280
+ gasPrice,
281
+ priorityFee,
282
+ chainId: chainContext.chainId,
283
+ txType
284
+ });
285
+ const buyTx = buildTransactionRequest(buyUnsigned, {
286
+ from: buyer.address,
287
+ nonce: noncePlan.buyerNonce,
288
+ gasLimit: finalGasLimit,
289
+ gasPrice,
290
+ priorityFee,
291
+ chainId: chainContext.chainId,
292
+ txType,
293
+ value: buyerAvailableFunds // ✅ 修复:使用 buyerAvailableFunds
294
+ });
295
+ [signedSell, signedBuy] = await Promise.all([
296
+ seller.signTransaction(sellTx),
297
+ buyer.signTransaction(buyTx)
298
+ ]);
299
+ }
300
+ // ... (Profit Tx) ...
150
301
  const profitTx = await buildProfitTransaction({
151
302
  seller,
152
303
  profitAmount,
@@ -159,7 +310,18 @@ export async function flapBundleSwapMerkle(params) {
159
310
  const allTransactions = [];
160
311
  if (approvalTx)
161
312
  allTransactions.push(approvalTx);
162
- allTransactions.push(signedSell, signedBuy);
313
+ // ✅ 根据模式组装交易
314
+ if (routing && sellerTransferTx) {
315
+ // 路由模式: Sell -> SellerTransfer -> RouteTxs -> Buy
316
+ allTransactions.push(signedSell);
317
+ allTransactions.push(sellerTransferTx);
318
+ allTransactions.push(...routeTxs);
319
+ allTransactions.push(signedBuy);
320
+ }
321
+ else {
322
+ // 普通模式: Sell -> Buy
323
+ allTransactions.push(signedSell, signedBuy);
324
+ }
163
325
  if (profitTx)
164
326
  allTransactions.push(profitTx);
165
327
  return {
@@ -168,9 +330,108 @@ export async function flapBundleSwapMerkle(params) {
168
330
  sellerAddress: seller.address,
169
331
  buyerAddress: buyer.address,
170
332
  sellAmount: ethers.formatUnits(sellAmountWei, decimals),
171
- buyAmount: ethers.formatEther(buyerNeed.maxBuyerValue),
333
+ buyAmount: ethers.formatEther(buyerAvailableFunds),
172
334
  hasApproval: !!approvalTx,
173
- profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
335
+ profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
336
+ // ✅ 路由元数据
337
+ routing: routing ? {
338
+ mainAddress: new Wallet(routing.mainPrivateKey).address,
339
+ hopAddresses: hopInfo?.addresses || [],
340
+ totalHops: hopInfo?.count || 0
341
+ } : undefined
342
+ }
343
+ };
344
+ }
345
+ async function buildFundingRoute({ routing, initialAmount, seller, buyerAddress, gasPrice, chainId, txType, nonceManager }) {
346
+ const mainWallet = new Wallet(routing.mainPrivateKey, seller.provider);
347
+ const signedTxs = [];
348
+ // 每笔转账消耗 Gas (21000 * gasPrice)
349
+ const transferGasCost = 21000n * gasPrice;
350
+ let currentAmount = initialAmount;
351
+ let currentFrom = mainWallet;
352
+ let currentNonce = await nonceManager.getNextNonce(mainWallet);
353
+ const hops = routing.hops || { count: 0 };
354
+ const hopCount = hops.count || (hops.privateKeys?.length ?? 0);
355
+ const hopAddresses = [];
356
+ // 如果有中间跳板
357
+ if (hopCount > 0) {
358
+ const hopWallets = [];
359
+ // 生成或使用指定的跳板钱包
360
+ for (let i = 0; i < hopCount; i++) {
361
+ let wallet;
362
+ if (hops.privateKeys && hops.privateKeys[i]) {
363
+ wallet = new Wallet(hops.privateKeys[i], seller.provider);
364
+ }
365
+ else {
366
+ const randomWallet = Wallet.createRandom(seller.provider);
367
+ wallet = new Wallet(randomWallet.privateKey, seller.provider); // ✅ 通过私钥重新创建 Wallet 实例以匹配类型
368
+ }
369
+ hopWallets.push(wallet);
370
+ hopAddresses.push(wallet.address);
371
+ }
372
+ // 1. Main -> Hop 1
373
+ // 需要确保 currentAmount 足够扣除 Gas
374
+ if (currentAmount <= transferGasCost)
375
+ throw new Error('资金不足以支付路由 Gas');
376
+ let sendAmount = currentAmount - transferGasCost; // 扣除本步 Gas
377
+ // 注意:如果是新生成的钱包,需要从上一步收到的钱里扣除下一步的 Gas
378
+ // 实际上每一跳的转账金额应该是:收到金额 - (下一步转账的 Gas)
379
+ // 最后一跳给 Buyer 的金额是剩余全部
380
+ // Main -> Hop1
381
+ signedTxs.push(await currentFrom.signTransaction({
382
+ to: hopWallets[0].address,
383
+ value: sendAmount,
384
+ nonce: currentNonce,
385
+ gasLimit: 21000n,
386
+ gasPrice,
387
+ chainId,
388
+ type: txType
389
+ }));
390
+ currentAmount = sendAmount;
391
+ // Hop i -> Hop i+1 (or Buyer)
392
+ for (let i = 0; i < hopCount; i++) {
393
+ const isLastHop = i === hopCount - 1;
394
+ const targetAddress = isLastHop ? buyerAddress : hopWallets[i + 1].address;
395
+ const fromWallet = hopWallets[i];
396
+ if (currentAmount <= transferGasCost)
397
+ throw new Error('路由过程中资金耗尽');
398
+ sendAmount = currentAmount - transferGasCost;
399
+ const nonce = await nonceManager.getNextNonce(fromWallet);
400
+ signedTxs.push(await fromWallet.signTransaction({
401
+ to: targetAddress,
402
+ value: sendAmount,
403
+ nonce,
404
+ gasLimit: 21000n,
405
+ gasPrice,
406
+ chainId,
407
+ type: txType
408
+ }));
409
+ currentAmount = sendAmount;
410
+ }
411
+ }
412
+ else {
413
+ // 没有中间跳板,直接 Main -> Buyer
414
+ // Main 收到了钱,现在转给 Buyer
415
+ if (currentAmount <= transferGasCost)
416
+ throw new Error('资金不足以支付主钱包转账 Gas');
417
+ const sendAmount = currentAmount - transferGasCost;
418
+ signedTxs.push(await currentFrom.signTransaction({
419
+ to: buyerAddress,
420
+ value: sendAmount,
421
+ nonce: currentNonce,
422
+ gasLimit: 21000n,
423
+ gasPrice,
424
+ chainId,
425
+ type: txType
426
+ }));
427
+ currentAmount = sendAmount;
428
+ }
429
+ return {
430
+ signedTxs,
431
+ finalAmount: currentAmount,
432
+ hopInfo: {
433
+ count: hopCount,
434
+ addresses: hopAddresses
174
435
  }
175
436
  };
176
437
  }
@@ -276,3 +276,49 @@ export type PancakeProxyApprovalBatchResult = {
276
276
  bundleHash?: string;
277
277
  message: string;
278
278
  };
279
+ /** ✅ 资金路由配置 */
280
+ export type FundRoutingConfig = {
281
+ /** 主钱包私钥(必填):Seller 的资金会先归集到这里,再分发给 Buyer */
282
+ mainPrivateKey: string;
283
+ /** 中间跳板配置(可选):用于混淆资金链路 */
284
+ hops?: {
285
+ /** 跳板数量(如果指定,将自动生成随机钱包) */
286
+ count?: number;
287
+ /** 指定跳板钱包私钥(如果指定,优先使用,长度必须等于 count) */
288
+ privateKeys?: string[];
289
+ };
290
+ };
291
+ /** ✅ Flap 捆绑换手参数(仅签名版本) */
292
+ export type FlapBundleSwapSignParams = {
293
+ chain: 'bsc' | 'opbnb';
294
+ sellerPrivateKey: string;
295
+ sellAmount?: string;
296
+ sellPercentage?: number;
297
+ buyerPrivateKey: string;
298
+ tokenAddress: string;
299
+ config: FlapSignConfig & {
300
+ slippageBps?: number;
301
+ reserveGasETH?: string;
302
+ skipQuoteOnError?: boolean;
303
+ skipApprovalCheck?: boolean;
304
+ };
305
+ /** 资金路由配置(可选):如果有,将执行 卖出->主钱包->跳板->买家->买入 的完整链路 */
306
+ routing?: FundRoutingConfig;
307
+ };
308
+ /** ✅ Flap 换手结果 */
309
+ export type FlapSwapResult = MerkleSignedResult & {
310
+ metadata: {
311
+ sellerAddress: string;
312
+ buyerAddress: string;
313
+ sellAmount: string;
314
+ buyAmount: string;
315
+ hasApproval: boolean;
316
+ profitAmount?: string;
317
+ /** 路由信息 */
318
+ routing?: {
319
+ mainAddress: string;
320
+ hopAddresses: string[];
321
+ totalHops: number;
322
+ };
323
+ };
324
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.2.87",
3
+ "version": "1.2.88",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",