four-flap-meme-sdk 1.4.77 → 1.4.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.d.ts +4 -2
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +195 -12
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +16 -0
- package/dist/flap/portal-bundle-merkle/encryption.js +146 -0
- package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +4 -2
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +222 -9
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/pancake/bundle-buy-first.d.ts +4 -2
- package/dist/pancake/bundle-buy-first.js +293 -14
- package/package.json +3 -38
- package/dist/sol/constants.d.ts +0 -126
- package/dist/sol/constants.js +0 -145
- package/dist/sol/dex/index.d.ts +0 -8
- package/dist/sol/dex/index.js +0 -12
- package/dist/sol/dex/meteora/client.d.ts +0 -76
- package/dist/sol/dex/meteora/client.js +0 -219
- package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +0 -61
- package/dist/sol/dex/meteora/damm-v1-bundle.js +0 -112
- package/dist/sol/dex/meteora/damm-v1.d.ts +0 -118
- package/dist/sol/dex/meteora/damm-v1.js +0 -315
- package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +0 -82
- package/dist/sol/dex/meteora/damm-v2-bundle.js +0 -242
- package/dist/sol/dex/meteora/damm-v2.d.ts +0 -172
- package/dist/sol/dex/meteora/damm-v2.js +0 -632
- package/dist/sol/dex/meteora/dbc-bundle.d.ts +0 -123
- package/dist/sol/dex/meteora/dbc-bundle.js +0 -304
- package/dist/sol/dex/meteora/dbc.d.ts +0 -192
- package/dist/sol/dex/meteora/dbc.js +0 -619
- package/dist/sol/dex/meteora/dlmm-bundle.d.ts +0 -39
- package/dist/sol/dex/meteora/dlmm-bundle.js +0 -189
- package/dist/sol/dex/meteora/dlmm.d.ts +0 -157
- package/dist/sol/dex/meteora/dlmm.js +0 -671
- package/dist/sol/dex/meteora/index.d.ts +0 -25
- package/dist/sol/dex/meteora/index.js +0 -65
- package/dist/sol/dex/meteora/types.d.ts +0 -787
- package/dist/sol/dex/meteora/types.js +0 -110
- package/dist/sol/dex/orca/index.d.ts +0 -10
- package/dist/sol/dex/orca/index.js +0 -16
- package/dist/sol/dex/orca/orca-bundle.d.ts +0 -41
- package/dist/sol/dex/orca/orca-bundle.js +0 -173
- package/dist/sol/dex/orca/orca.d.ts +0 -65
- package/dist/sol/dex/orca/orca.js +0 -474
- package/dist/sol/dex/orca/types.d.ts +0 -263
- package/dist/sol/dex/orca/types.js +0 -38
- package/dist/sol/dex/orca/wavebreak-bundle.d.ts +0 -34
- package/dist/sol/dex/orca/wavebreak-bundle.js +0 -198
- package/dist/sol/dex/orca/wavebreak-types.d.ts +0 -227
- package/dist/sol/dex/orca/wavebreak-types.js +0 -23
- package/dist/sol/dex/orca/wavebreak.d.ts +0 -78
- package/dist/sol/dex/orca/wavebreak.js +0 -497
- package/dist/sol/dex/pump/index.d.ts +0 -9
- package/dist/sol/dex/pump/index.js +0 -14
- package/dist/sol/dex/pump/pump-bundle.d.ts +0 -92
- package/dist/sol/dex/pump/pump-bundle.js +0 -383
- package/dist/sol/dex/pump/pump-swap-bundle.d.ts +0 -103
- package/dist/sol/dex/pump/pump-swap-bundle.js +0 -380
- package/dist/sol/dex/pump/pump-swap.d.ts +0 -46
- package/dist/sol/dex/pump/pump-swap.js +0 -199
- package/dist/sol/dex/pump/pump.d.ts +0 -35
- package/dist/sol/dex/pump/pump.js +0 -352
- package/dist/sol/dex/pump/types.d.ts +0 -215
- package/dist/sol/dex/pump/types.js +0 -5
- package/dist/sol/dex/raydium/index.d.ts +0 -8
- package/dist/sol/dex/raydium/index.js +0 -12
- package/dist/sol/dex/raydium/launchlab.d.ts +0 -68
- package/dist/sol/dex/raydium/launchlab.js +0 -210
- package/dist/sol/dex/raydium/raydium-bundle.d.ts +0 -64
- package/dist/sol/dex/raydium/raydium-bundle.js +0 -324
- package/dist/sol/dex/raydium/raydium.d.ts +0 -40
- package/dist/sol/dex/raydium/raydium.js +0 -366
- package/dist/sol/dex/raydium/types.d.ts +0 -240
- package/dist/sol/dex/raydium/types.js +0 -5
- package/dist/sol/index.d.ts +0 -10
- package/dist/sol/index.js +0 -16
- package/dist/sol/jito/bundle.d.ts +0 -90
- package/dist/sol/jito/bundle.js +0 -263
- package/dist/sol/jito/index.d.ts +0 -7
- package/dist/sol/jito/index.js +0 -7
- package/dist/sol/jito/tip.d.ts +0 -51
- package/dist/sol/jito/tip.js +0 -83
- package/dist/sol/jito/types.d.ts +0 -100
- package/dist/sol/jito/types.js +0 -5
- package/dist/sol/token/create-complete.d.ts +0 -115
- package/dist/sol/token/create-complete.js +0 -235
- package/dist/sol/token/create-token.d.ts +0 -57
- package/dist/sol/token/create-token.js +0 -230
- package/dist/sol/token/index.d.ts +0 -9
- package/dist/sol/token/index.js +0 -14
- package/dist/sol/token/metadata-upload.d.ts +0 -86
- package/dist/sol/token/metadata-upload.js +0 -173
- package/dist/sol/token/metadata.d.ts +0 -92
- package/dist/sol/token/metadata.js +0 -274
- package/dist/sol/token/types.d.ts +0 -153
- package/dist/sol/token/types.js +0 -5
- package/dist/sol/types.d.ts +0 -176
- package/dist/sol/types.js +0 -7
- package/dist/sol/utils/balance.d.ts +0 -160
- package/dist/sol/utils/balance.js +0 -638
- package/dist/sol/utils/connection.d.ts +0 -78
- package/dist/sol/utils/connection.js +0 -168
- package/dist/sol/utils/index.d.ts +0 -9
- package/dist/sol/utils/index.js +0 -9
- package/dist/sol/utils/lp-inspect.d.ts +0 -129
- package/dist/sol/utils/lp-inspect.js +0 -900
- package/dist/sol/utils/transfer.d.ts +0 -125
- package/dist/sol/utils/transfer.js +0 -220
- package/dist/sol/utils/wallet.d.ts +0 -107
- package/dist/sol/utils/wallet.js +0 -210
|
@@ -18,10 +18,12 @@ export interface FourBuyFirstConfig extends CommonBundleConfig {
|
|
|
18
18
|
waitTimeoutMs?: number;
|
|
19
19
|
}
|
|
20
20
|
export interface FourBundleBuyFirstSignParams {
|
|
21
|
-
buyerPrivateKey
|
|
21
|
+
buyerPrivateKey?: string;
|
|
22
|
+
buyerPrivateKeys?: string[];
|
|
22
23
|
buyerFunds?: string;
|
|
23
24
|
buyerFundsPercentage?: number;
|
|
24
|
-
sellerPrivateKey
|
|
25
|
+
sellerPrivateKey?: string;
|
|
26
|
+
sellerPrivateKeys?: string[];
|
|
25
27
|
tokenAddress: string;
|
|
26
28
|
config: FourBuyFirstSignConfig;
|
|
27
29
|
buyCount?: number;
|
|
@@ -26,8 +26,8 @@ const BRIBE_TX_COUNT = 1;
|
|
|
26
26
|
const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
|
|
27
27
|
/** 最大买卖交易数 */
|
|
28
28
|
const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
|
|
29
|
-
/** 每笔交易利润比例(基点):
|
|
30
|
-
const PROFIT_RATE_PER_TX_BPS =
|
|
29
|
+
/** 每笔交易利润比例(基点):6 bps = 0.06% = 万分之六 */
|
|
30
|
+
const PROFIT_RATE_PER_TX_BPS = 6;
|
|
31
31
|
/**
|
|
32
32
|
* 验证买卖笔数
|
|
33
33
|
*/
|
|
@@ -83,10 +83,26 @@ function splitAmount(totalAmount, count) {
|
|
|
83
83
|
return amounts;
|
|
84
84
|
}
|
|
85
85
|
export async function fourBundleBuyFirstMerkle(params) {
|
|
86
|
-
const { buyerPrivateKey, buyerFunds, buyerFundsPercentage, sellerPrivateKey, tokenAddress, config, buyCount: _buyCount, sellCount: _sellCount } = params;
|
|
87
|
-
// ✅
|
|
86
|
+
const { buyerPrivateKey, buyerPrivateKeys, buyerFunds, buyerFundsPercentage, sellerPrivateKey, sellerPrivateKeys, tokenAddress, config, buyCount: _buyCount, sellCount: _sellCount } = params;
|
|
87
|
+
// ✅ 判断是否为多钱包模式
|
|
88
|
+
const isMultiWalletMode = !!(buyerPrivateKeys && buyerPrivateKeys.length > 0) ||
|
|
89
|
+
!!(sellerPrivateKeys && sellerPrivateKeys.length > 0);
|
|
88
90
|
const buyCount = _buyCount ?? 1;
|
|
89
91
|
const sellCount = _sellCount ?? 1;
|
|
92
|
+
// ✅ 多钱包模式:使用单独的处理逻辑
|
|
93
|
+
if (isMultiWalletMode) {
|
|
94
|
+
return await fourBundleBuyFirstMultiWallet({
|
|
95
|
+
buyerPrivateKeys: buyerPrivateKeys || (buyerPrivateKey ? [buyerPrivateKey] : []),
|
|
96
|
+
sellerPrivateKeys: sellerPrivateKeys || (sellerPrivateKey ? [sellerPrivateKey] : []),
|
|
97
|
+
tokenAddress,
|
|
98
|
+
buyerFunds,
|
|
99
|
+
config
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// ✅ 单钱包模式(向后兼容)
|
|
103
|
+
if (!buyerPrivateKey || !sellerPrivateKey) {
|
|
104
|
+
throw new Error('单钱包模式需要提供 buyerPrivateKey 和 sellerPrivateKey');
|
|
105
|
+
}
|
|
90
106
|
validateSwapCounts(buyCount, sellCount);
|
|
91
107
|
// ✅ 计算利润比例:每笔万分之3
|
|
92
108
|
const totalTxCount = buyCount + sellCount;
|
|
@@ -139,14 +155,8 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
139
155
|
if (!estimatedTokenAmount || estimatedTokenAmount <= 0n) {
|
|
140
156
|
throw new Error('报价失败:无法估算可买入的代币数量');
|
|
141
157
|
}
|
|
142
|
-
// ✅
|
|
143
|
-
|
|
144
|
-
// 滑点比例:buyCount * 1%(每多一笔买入增加 1% 保护)
|
|
145
|
-
let sellAmountWei = estimatedTokenAmount;
|
|
146
|
-
if (buyCount > 1 || sellCount > 1) {
|
|
147
|
-
const slippageBps = BigInt(buyCount * 100); // buyCount=3 → 3%
|
|
148
|
-
sellAmountWei = estimatedTokenAmount * (10000n - slippageBps) / 10000n;
|
|
149
|
-
}
|
|
158
|
+
// ✅ 卖出数量直接使用估算的代币数量(无滑点保护)
|
|
159
|
+
const sellAmountWei = estimatedTokenAmount;
|
|
150
160
|
// 卖方余额检查
|
|
151
161
|
if (!sameAddress && sellerTokenBal < sellAmountWei) {
|
|
152
162
|
throw new Error(`卖方代币余额不足: 需要 ${ethers.formatUnits(sellAmountWei, decimals)},实际 ${ethers.formatUnits(sellerTokenBal, decimals)}`);
|
|
@@ -298,3 +308,176 @@ async function planMultiNonces({ buyer, seller, buyCount, sellCount, extractProf
|
|
|
298
308
|
const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
|
|
299
309
|
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
300
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* ✅ Four 多钱包捆绑换手
|
|
313
|
+
* - 多个买方钱包执行买入(每个钱包1笔)
|
|
314
|
+
* - 多个卖方钱包执行卖出(每个钱包1笔)
|
|
315
|
+
* - 买入总价值 = 卖出总价值
|
|
316
|
+
*/
|
|
317
|
+
async function fourBundleBuyFirstMultiWallet(params) {
|
|
318
|
+
const { buyerPrivateKeys, sellerPrivateKeys, tokenAddress, buyerFunds, config } = params;
|
|
319
|
+
const buyCount = buyerPrivateKeys.length;
|
|
320
|
+
const sellCount = sellerPrivateKeys.length;
|
|
321
|
+
if (buyCount === 0)
|
|
322
|
+
throw new Error('买方钱包数量不能为0');
|
|
323
|
+
if (sellCount === 0)
|
|
324
|
+
throw new Error('卖方钱包数量不能为0');
|
|
325
|
+
// 验证总交易数不超过限制
|
|
326
|
+
validateSwapCounts(buyCount, sellCount);
|
|
327
|
+
// ✅ 计算利润比例:每笔万分之6
|
|
328
|
+
const totalTxCount = buyCount + sellCount;
|
|
329
|
+
const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
|
|
330
|
+
const chainIdNum = config.chainId ?? 56;
|
|
331
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
|
|
332
|
+
chainId: chainIdNum,
|
|
333
|
+
name: 'BSC'
|
|
334
|
+
});
|
|
335
|
+
const nonceManager = new NonceManager(provider);
|
|
336
|
+
// 创建所有钱包实例
|
|
337
|
+
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, provider));
|
|
338
|
+
const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, provider));
|
|
339
|
+
// 使用第一个卖方作为主卖方(支付贿赂和利润)
|
|
340
|
+
const mainSeller = sellers[0];
|
|
341
|
+
// ✅ 计算总交易金额
|
|
342
|
+
if (!buyerFunds) {
|
|
343
|
+
throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
|
|
344
|
+
}
|
|
345
|
+
const totalFundsWei = ethers.parseEther(String(buyerFunds));
|
|
346
|
+
if (totalFundsWei <= 0n) {
|
|
347
|
+
throw new Error('交易金额必须大于0');
|
|
348
|
+
}
|
|
349
|
+
// ✅ 获取报价:买入能获得多少代币
|
|
350
|
+
const helper3 = new Contract(ADDRESSES.BSC.TokenManagerHelper3, HELPER3_ABI, provider);
|
|
351
|
+
const buyQuote = await helper3.tryBuy(tokenAddress, 0n, totalFundsWei);
|
|
352
|
+
const estimatedTokenAmount = buyQuote.estimatedAmount ?? buyQuote[2];
|
|
353
|
+
if (!estimatedTokenAmount || estimatedTokenAmount <= 0n) {
|
|
354
|
+
throw new Error('报价失败:无法估算可买入的代币数量');
|
|
355
|
+
}
|
|
356
|
+
// ✅ 将总金额平均分配给买方
|
|
357
|
+
const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
|
|
358
|
+
// ✅ 将代币平均分配给卖方
|
|
359
|
+
const sellAmountsWei = splitAmount(estimatedTokenAmount, sellCount);
|
|
360
|
+
const finalGasLimit = getGasLimit(config);
|
|
361
|
+
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
362
|
+
const txType = getTxType(config);
|
|
363
|
+
// ✅ 估算利润
|
|
364
|
+
const sellResult = await trySell('BSC', config.rpcUrl, tokenAddress, estimatedTokenAmount);
|
|
365
|
+
const estimatedSellFunds = sellResult.funds;
|
|
366
|
+
const profitAmount = (estimatedSellFunds * BigInt(profitRateBps)) / 10000n;
|
|
367
|
+
// ✅ 获取贿赂金额
|
|
368
|
+
const bribeAmount = getBribeAmount(config);
|
|
369
|
+
const needBribeTx = bribeAmount > 0n;
|
|
370
|
+
// ✅ 获取所有钱包的 nonces
|
|
371
|
+
const noncesMap = new Map();
|
|
372
|
+
await Promise.all([...sellers, ...buyers].map(async (wallet) => {
|
|
373
|
+
const addr = wallet.address.toLowerCase();
|
|
374
|
+
if (!noncesMap.has(addr)) {
|
|
375
|
+
const nonce = await nonceManager.getNextNonce(wallet);
|
|
376
|
+
noncesMap.set(addr, nonce);
|
|
377
|
+
}
|
|
378
|
+
}));
|
|
379
|
+
// ✅ 构建交易列表
|
|
380
|
+
const allTransactions = [];
|
|
381
|
+
// 1. 贿赂交易(由主卖方支付)
|
|
382
|
+
if (needBribeTx) {
|
|
383
|
+
const mainSellerAddr = mainSeller.address.toLowerCase();
|
|
384
|
+
const bribeNonce = noncesMap.get(mainSellerAddr);
|
|
385
|
+
noncesMap.set(mainSellerAddr, bribeNonce + 1);
|
|
386
|
+
const bribeTx = await mainSeller.signTransaction({
|
|
387
|
+
to: BLOCKRAZOR_BUILDER_EOA,
|
|
388
|
+
value: bribeAmount,
|
|
389
|
+
nonce: bribeNonce,
|
|
390
|
+
gasPrice,
|
|
391
|
+
gasLimit: 21000n,
|
|
392
|
+
chainId: chainIdNum,
|
|
393
|
+
type: txType
|
|
394
|
+
});
|
|
395
|
+
allTransactions.push(bribeTx);
|
|
396
|
+
}
|
|
397
|
+
// 2. 构建所有买入交易
|
|
398
|
+
const tm = new Contract(TM_ADDRESS, TM_ABI, provider);
|
|
399
|
+
const buyTxPromises = buyers.map(async (buyer, i) => {
|
|
400
|
+
const buyAmount = buyAmountsWei[i];
|
|
401
|
+
const buyerAddr = buyer.address.toLowerCase();
|
|
402
|
+
const nonce = noncesMap.get(buyerAddr);
|
|
403
|
+
noncesMap.set(buyerAddr, nonce + 1);
|
|
404
|
+
const tmBuyer = tm.connect(buyer);
|
|
405
|
+
const unsigned = await tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, buyAmount, 0n, { value: buyAmount });
|
|
406
|
+
return buyer.signTransaction({
|
|
407
|
+
...unsigned,
|
|
408
|
+
from: buyer.address,
|
|
409
|
+
nonce,
|
|
410
|
+
gasLimit: finalGasLimit,
|
|
411
|
+
gasPrice,
|
|
412
|
+
chainId: chainIdNum,
|
|
413
|
+
type: txType,
|
|
414
|
+
value: buyAmount
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
// 3. 构建所有卖出交易
|
|
418
|
+
const sellTxPromises = sellers.map(async (seller, i) => {
|
|
419
|
+
const sellAmount = sellAmountsWei[i];
|
|
420
|
+
const sellerAddr = seller.address.toLowerCase();
|
|
421
|
+
const nonce = noncesMap.get(sellerAddr);
|
|
422
|
+
noncesMap.set(sellerAddr, nonce + 1);
|
|
423
|
+
const tmSeller = tm.connect(seller);
|
|
424
|
+
const unsigned = await tmSeller.sellToken.populateTransaction(0n, tokenAddress, sellAmount, 0n);
|
|
425
|
+
return seller.signTransaction({
|
|
426
|
+
...unsigned,
|
|
427
|
+
from: seller.address,
|
|
428
|
+
nonce,
|
|
429
|
+
gasLimit: finalGasLimit,
|
|
430
|
+
gasPrice,
|
|
431
|
+
chainId: chainIdNum,
|
|
432
|
+
type: txType,
|
|
433
|
+
value: 0n
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
// ✅ 并行签名所有买卖交易
|
|
437
|
+
const [signedBuys, signedSells] = await Promise.all([
|
|
438
|
+
Promise.all(buyTxPromises),
|
|
439
|
+
Promise.all(sellTxPromises)
|
|
440
|
+
]);
|
|
441
|
+
// 先买后卖:买入交易在前
|
|
442
|
+
allTransactions.push(...signedBuys, ...signedSells);
|
|
443
|
+
// 4. 利润多跳转账(由主卖方支付)
|
|
444
|
+
let profitHopWallets;
|
|
445
|
+
if (profitAmount > 0n) {
|
|
446
|
+
const mainSellerAddr = mainSeller.address.toLowerCase();
|
|
447
|
+
const profitNonce = noncesMap.get(mainSellerAddr);
|
|
448
|
+
const profitHopResult = await buildProfitHopTransactions({
|
|
449
|
+
provider,
|
|
450
|
+
payerWallet: mainSeller,
|
|
451
|
+
profitAmount,
|
|
452
|
+
profitRecipient: getProfitRecipient(),
|
|
453
|
+
hopCount: PROFIT_HOP_COUNT,
|
|
454
|
+
gasPrice,
|
|
455
|
+
chainId: chainIdNum,
|
|
456
|
+
txType,
|
|
457
|
+
startNonce: profitNonce
|
|
458
|
+
});
|
|
459
|
+
allTransactions.push(...profitHopResult.signedTransactions);
|
|
460
|
+
profitHopWallets = profitHopResult.hopWallets;
|
|
461
|
+
}
|
|
462
|
+
nonceManager.clearTemp();
|
|
463
|
+
// 获取代币精度用于显示
|
|
464
|
+
const erc20 = new Contract(tokenAddress, [
|
|
465
|
+
'function decimals() view returns (uint8)'
|
|
466
|
+
], provider);
|
|
467
|
+
const decimals = await erc20.decimals();
|
|
468
|
+
return {
|
|
469
|
+
signedTransactions: allTransactions,
|
|
470
|
+
profitHopWallets,
|
|
471
|
+
metadata: {
|
|
472
|
+
buyerAddress: buyers.map(b => b.address).join(','),
|
|
473
|
+
sellerAddress: sellers.map(s => s.address).join(','),
|
|
474
|
+
buyAmount: ethers.formatEther(totalFundsWei),
|
|
475
|
+
sellAmount: ethers.formatUnits(estimatedTokenAmount, decimals),
|
|
476
|
+
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
|
|
477
|
+
buyCount,
|
|
478
|
+
sellCount,
|
|
479
|
+
buyAmounts: buyAmountsWei.map(amt => ethers.formatEther(amt)),
|
|
480
|
+
sellAmounts: sellAmountsWei.map(amt => ethers.formatUnits(amt, decimals))
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECDH + AES-GCM 加密工具(浏览器兼容)
|
|
3
|
+
* 用于将签名交易用服务器公钥加密
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 用服务器公钥加密签名交易(ECDH + AES-GCM)
|
|
7
|
+
*
|
|
8
|
+
* @param signedTransactions 签名后的交易数组
|
|
9
|
+
* @param publicKeyBase64 服务器提供的公钥(Base64 格式)
|
|
10
|
+
* @returns JSON 字符串 {e: 临时公钥, i: IV, d: 密文}
|
|
11
|
+
*/
|
|
12
|
+
export declare function encryptWithPublicKey(signedTransactions: string[], publicKeyBase64: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* 验证公钥格式(Base64)
|
|
15
|
+
*/
|
|
16
|
+
export declare function validatePublicKey(publicKeyBase64: string): boolean;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECDH + AES-GCM 加密工具(浏览器兼容)
|
|
3
|
+
* 用于将签名交易用服务器公钥加密
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 获取全局 crypto 对象(最简单直接的方式)
|
|
7
|
+
*/
|
|
8
|
+
function getCryptoAPI() {
|
|
9
|
+
// 尝试所有可能的全局对象,优先浏览器环境
|
|
10
|
+
const cryptoObj = (typeof window !== 'undefined' && window.crypto) ||
|
|
11
|
+
(typeof self !== 'undefined' && self.crypto) ||
|
|
12
|
+
(typeof global !== 'undefined' && global.crypto) ||
|
|
13
|
+
(typeof globalThis !== 'undefined' && globalThis.crypto);
|
|
14
|
+
if (!cryptoObj) {
|
|
15
|
+
const env = typeof window !== 'undefined' ? 'Browser' : 'Node.js';
|
|
16
|
+
const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
|
|
17
|
+
throw new Error(`❌ Crypto API 不可用。环境: ${env}, 协议: ${protocol}. ` +
|
|
18
|
+
'请确保在 HTTPS 或 localhost 下运行');
|
|
19
|
+
}
|
|
20
|
+
return cryptoObj;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 获取 SubtleCrypto(用于加密操作)
|
|
24
|
+
*/
|
|
25
|
+
function getSubtleCrypto() {
|
|
26
|
+
const crypto = getCryptoAPI();
|
|
27
|
+
if (!crypto.subtle) {
|
|
28
|
+
const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
|
|
29
|
+
const hostname = typeof location !== 'undefined' ? location.hostname : 'unknown';
|
|
30
|
+
throw new Error(`❌ SubtleCrypto API 不可用。协议: ${protocol}, 主机: ${hostname}. ` +
|
|
31
|
+
'请确保:1) 使用 HTTPS (或 localhost);2) 浏览器支持 Web Crypto API;' +
|
|
32
|
+
'3) 不在无痕/隐私浏览模式下');
|
|
33
|
+
}
|
|
34
|
+
return crypto.subtle;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Base64 转 ArrayBuffer(优先使用浏览器 API)
|
|
38
|
+
*/
|
|
39
|
+
function base64ToArrayBuffer(base64) {
|
|
40
|
+
// 浏览器环境(优先)
|
|
41
|
+
if (typeof atob !== 'undefined') {
|
|
42
|
+
const binaryString = atob(base64);
|
|
43
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
44
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
45
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
46
|
+
}
|
|
47
|
+
return bytes.buffer;
|
|
48
|
+
}
|
|
49
|
+
// Node.js 环境(fallback)
|
|
50
|
+
if (typeof Buffer !== 'undefined') {
|
|
51
|
+
return Buffer.from(base64, 'base64').buffer;
|
|
52
|
+
}
|
|
53
|
+
throw new Error('❌ Base64 解码不可用');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* ArrayBuffer 转 Base64(优先使用浏览器 API)
|
|
57
|
+
*/
|
|
58
|
+
function arrayBufferToBase64(buffer) {
|
|
59
|
+
// 浏览器环境(优先)
|
|
60
|
+
if (typeof btoa !== 'undefined') {
|
|
61
|
+
const bytes = new Uint8Array(buffer);
|
|
62
|
+
let binary = '';
|
|
63
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
64
|
+
binary += String.fromCharCode(bytes[i]);
|
|
65
|
+
}
|
|
66
|
+
return btoa(binary);
|
|
67
|
+
}
|
|
68
|
+
// Node.js 环境(fallback)
|
|
69
|
+
if (typeof Buffer !== 'undefined') {
|
|
70
|
+
return Buffer.from(buffer).toString('base64');
|
|
71
|
+
}
|
|
72
|
+
throw new Error('❌ Base64 编码不可用');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 生成随机 Hex 字符串
|
|
76
|
+
*/
|
|
77
|
+
function randomHex(length) {
|
|
78
|
+
const crypto = getCryptoAPI();
|
|
79
|
+
const array = new Uint8Array(length);
|
|
80
|
+
crypto.getRandomValues(array);
|
|
81
|
+
return Array.from(array)
|
|
82
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
83
|
+
.join('');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 用服务器公钥加密签名交易(ECDH + AES-GCM)
|
|
87
|
+
*
|
|
88
|
+
* @param signedTransactions 签名后的交易数组
|
|
89
|
+
* @param publicKeyBase64 服务器提供的公钥(Base64 格式)
|
|
90
|
+
* @returns JSON 字符串 {e: 临时公钥, i: IV, d: 密文}
|
|
91
|
+
*/
|
|
92
|
+
export async function encryptWithPublicKey(signedTransactions, publicKeyBase64) {
|
|
93
|
+
try {
|
|
94
|
+
// 0. 获取 SubtleCrypto 和 Crypto API
|
|
95
|
+
const subtle = getSubtleCrypto();
|
|
96
|
+
const crypto = getCryptoAPI();
|
|
97
|
+
// 1. 准备数据
|
|
98
|
+
const payload = {
|
|
99
|
+
signedTransactions,
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
nonce: randomHex(8)
|
|
102
|
+
};
|
|
103
|
+
const plaintext = JSON.stringify(payload);
|
|
104
|
+
// 2. 生成临时 ECDH 密钥对
|
|
105
|
+
const ephemeralKeyPair = await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']);
|
|
106
|
+
// 3. 导入服务器公钥
|
|
107
|
+
const publicKeyBuffer = base64ToArrayBuffer(publicKeyBase64);
|
|
108
|
+
const publicKey = await subtle.importKey('raw', publicKeyBuffer, { name: 'ECDH', namedCurve: 'P-256' }, false, []);
|
|
109
|
+
// 4. 派生共享密钥(AES-256)
|
|
110
|
+
const sharedKey = await subtle.deriveKey({ name: 'ECDH', public: publicKey }, ephemeralKeyPair.privateKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt']);
|
|
111
|
+
// 5. AES-GCM 加密
|
|
112
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
113
|
+
const encrypted = await subtle.encrypt({ name: 'AES-GCM', iv }, sharedKey, new TextEncoder().encode(plaintext));
|
|
114
|
+
// 6. 导出临时公钥
|
|
115
|
+
const ephemeralPublicKeyRaw = await subtle.exportKey('raw', ephemeralKeyPair.publicKey);
|
|
116
|
+
// 7. 返回加密包(JSON 格式)
|
|
117
|
+
return JSON.stringify({
|
|
118
|
+
e: arrayBufferToBase64(ephemeralPublicKeyRaw), // 临时公钥
|
|
119
|
+
i: arrayBufferToBase64(iv.buffer), // IV
|
|
120
|
+
d: arrayBufferToBase64(encrypted) // 密文
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
throw new Error(`加密失败: ${error?.message || String(error)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 验证公钥格式(Base64)
|
|
129
|
+
*/
|
|
130
|
+
export function validatePublicKey(publicKeyBase64) {
|
|
131
|
+
try {
|
|
132
|
+
if (!publicKeyBase64)
|
|
133
|
+
return false;
|
|
134
|
+
// Base64 字符集验证
|
|
135
|
+
if (!/^[A-Za-z0-9+/=]+$/.test(publicKeyBase64))
|
|
136
|
+
return false;
|
|
137
|
+
// ECDH P-256 公钥固定长度 65 字节(未压缩)
|
|
138
|
+
// Base64 编码后约 88 字符
|
|
139
|
+
if (publicKeyBase64.length < 80 || publicKeyBase64.length > 100)
|
|
140
|
+
return false;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -22,8 +22,10 @@ export interface FlapBuyFirstConfig extends CommonBundleConfig {
|
|
|
22
22
|
}
|
|
23
23
|
export interface FlapBundleBuyFirstSignParams {
|
|
24
24
|
chain: FlapChain;
|
|
25
|
-
buyerPrivateKey
|
|
26
|
-
|
|
25
|
+
buyerPrivateKey?: string;
|
|
26
|
+
buyerPrivateKeys?: string[];
|
|
27
|
+
sellerPrivateKey?: string;
|
|
28
|
+
sellerPrivateKeys?: string[];
|
|
27
29
|
tokenAddress: string;
|
|
28
30
|
buyerFunds?: string;
|
|
29
31
|
buyerFundsPercentage?: number;
|