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
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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:
|
|
186
|
+
inputAmount: buyerAvailableFunds, // ✅ 使用计算出的可用资金
|
|
104
187
|
minOutputAmount: 0,
|
|
105
188
|
permitData: '0x'
|
|
106
|
-
}, { value:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
+
};
|