four-flap-meme-sdk 1.4.62 → 1.4.63
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 -0
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +133 -78
- 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 -0
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +160 -41
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/pancake/bundle-buy-first.d.ts +4 -0
- package/dist/pancake/bundle-buy-first.js +185 -35
- 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
|
@@ -24,6 +24,8 @@ export interface FourBundleBuyFirstSignParams {
|
|
|
24
24
|
sellerPrivateKey: string;
|
|
25
25
|
tokenAddress: string;
|
|
26
26
|
config: FourBuyFirstSignConfig;
|
|
27
|
+
buyCount?: number;
|
|
28
|
+
sellCount?: number;
|
|
27
29
|
}
|
|
28
30
|
export interface FourBundleBuyFirstParams {
|
|
29
31
|
buyerPrivateKey: string;
|
|
@@ -43,6 +45,8 @@ export type FourBuyFirstResult = {
|
|
|
43
45
|
buyAmount: string;
|
|
44
46
|
sellAmount: string;
|
|
45
47
|
profitAmount?: string;
|
|
48
|
+
buyCount?: number;
|
|
49
|
+
sellCount?: number;
|
|
46
50
|
};
|
|
47
51
|
};
|
|
48
52
|
export declare function fourBundleBuyFirstMerkle(params: FourBundleBuyFirstSignParams): Promise<FourBuyFirstResult>;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { ethers, Contract, Wallet } from 'ethers';
|
|
7
7
|
import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../utils/bundle-helpers.js';
|
|
8
|
-
import { ADDRESSES,
|
|
8
|
+
import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
|
|
9
9
|
import { TM_ABI, HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
|
|
10
10
|
import { getTxType, getGasPriceConfig, getProfitRecipient, getBribeAmount } from './config.js';
|
|
11
11
|
import { trySell } from '../tm.js';
|
|
@@ -17,8 +17,61 @@ function getGasLimit(config, defaultGas = 800000) {
|
|
|
17
17
|
const multiplier = config.gasLimitMultiplier ?? 1.0;
|
|
18
18
|
return BigInt(Math.ceil(defaultGas * multiplier));
|
|
19
19
|
}
|
|
20
|
+
// ==================== 多笔买卖常量 ====================
|
|
21
|
+
/** 最大 Bundle 签名数 */
|
|
22
|
+
const MAX_BUNDLE_SIGNATURES = 50;
|
|
23
|
+
/** 贿赂交易数 */
|
|
24
|
+
const BRIBE_TX_COUNT = 1;
|
|
25
|
+
/** 利润中转交易数(支付者 1 笔 + 中转钱包 PROFIT_HOP_COUNT 笔 + 最终接收 1 笔) */
|
|
26
|
+
const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
|
|
27
|
+
/** 最大买卖交易数 */
|
|
28
|
+
const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
|
|
29
|
+
/** 每笔交易利润比例(基点):3 bps = 0.03% = 万分之三 */
|
|
30
|
+
const PROFIT_RATE_PER_TX_BPS = 3;
|
|
31
|
+
/**
|
|
32
|
+
* 验证买卖笔数
|
|
33
|
+
*/
|
|
34
|
+
function validateSwapCounts(buyCount, sellCount) {
|
|
35
|
+
if (buyCount < 1) {
|
|
36
|
+
throw new Error(`买入笔数必须至少为 1,当前为 ${buyCount}`);
|
|
37
|
+
}
|
|
38
|
+
if (sellCount < 1) {
|
|
39
|
+
throw new Error(`卖出笔数必须至少为 1,当前为 ${sellCount}`);
|
|
40
|
+
}
|
|
41
|
+
const totalSwaps = buyCount + sellCount;
|
|
42
|
+
if (totalSwaps > MAX_SWAP_TX_COUNT) {
|
|
43
|
+
throw new Error(`买卖笔数总和 (${totalSwaps}) 超出限制 (${MAX_SWAP_TX_COUNT}),` +
|
|
44
|
+
`最大签名数为 ${MAX_BUNDLE_SIGNATURES}(${BRIBE_TX_COUNT} 贿赂 + ${PROFIT_TX_COUNT} 利润 + ${MAX_SWAP_TX_COUNT} 买卖)`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 将金额均匀拆分成多份
|
|
49
|
+
*/
|
|
50
|
+
function splitAmount(totalAmount, count) {
|
|
51
|
+
if (count <= 0) {
|
|
52
|
+
throw new Error('拆分份数必须大于 0');
|
|
53
|
+
}
|
|
54
|
+
if (count === 1) {
|
|
55
|
+
return [totalAmount];
|
|
56
|
+
}
|
|
57
|
+
const baseAmount = totalAmount / BigInt(count);
|
|
58
|
+
const remainder = totalAmount % BigInt(count);
|
|
59
|
+
const amounts = [];
|
|
60
|
+
for (let i = 0; i < count - 1; i++) {
|
|
61
|
+
amounts.push(baseAmount);
|
|
62
|
+
}
|
|
63
|
+
amounts.push(baseAmount + remainder);
|
|
64
|
+
return amounts;
|
|
65
|
+
}
|
|
20
66
|
export async function fourBundleBuyFirstMerkle(params) {
|
|
21
|
-
const { buyerPrivateKey, buyerFunds, buyerFundsPercentage, sellerPrivateKey, tokenAddress, config } = params;
|
|
67
|
+
const { buyerPrivateKey, buyerFunds, buyerFundsPercentage, sellerPrivateKey, tokenAddress, config, buyCount: _buyCount, sellCount: _sellCount } = params;
|
|
68
|
+
// ✅ 解析并验证买卖笔数
|
|
69
|
+
const buyCount = _buyCount ?? 1;
|
|
70
|
+
const sellCount = _sellCount ?? 1;
|
|
71
|
+
validateSwapCounts(buyCount, sellCount);
|
|
72
|
+
// ✅ 计算利润比例:每笔万分之3
|
|
73
|
+
const totalTxCount = buyCount + sellCount;
|
|
74
|
+
const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
|
|
22
75
|
const chainIdNum = config.chainId ?? 56;
|
|
23
76
|
const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
|
|
24
77
|
chainId: chainIdNum,
|
|
@@ -75,115 +128,82 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
75
128
|
// ✅ 优化:第三批并行 - trySell、构建交易、获取 nonces
|
|
76
129
|
const tmBuyer = new Contract(TM_ADDRESS, TM_ABI, buyer);
|
|
77
130
|
const tmSeller = new Contract(TM_ADDRESS, TM_ABI, seller);
|
|
131
|
+
// ✅ 拆分买入和卖出金额
|
|
132
|
+
const buyAmountsWei = splitAmount(buyerFundsWei, buyCount);
|
|
133
|
+
const sellAmountsWei = splitAmount(sellAmountWei, sellCount);
|
|
78
134
|
// 预先规划 nonces
|
|
79
135
|
const extractProfit = true;
|
|
80
|
-
const profitRateBps = PROFIT_CONFIG.RATE_BPS_SWAP; // 万分之六
|
|
81
136
|
// ✅ 获取贿赂金额
|
|
82
137
|
const bribeAmount = getBribeAmount(config);
|
|
83
138
|
const needBribeTx = bribeAmount > 0n;
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// seller 需要 sellerNonceCount 个连续 nonce,从初始 nonce 开始
|
|
100
|
-
sellerNonces: Array.from({ length: sellerNonceCount }, (_, i) => initialNonces[1] + i)
|
|
101
|
-
}));
|
|
102
|
-
const [sellResult, buyUnsigned, sellUnsigned, noncesResult] = await Promise.all([
|
|
139
|
+
// ✅ 构建多笔买入和卖出交易
|
|
140
|
+
const buyUnsignedPromises = buyAmountsWei.map(amount => tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, amount, 0n, { value: amount }));
|
|
141
|
+
const sellUnsignedPromises = sellAmountsWei.map(amount => tmSeller.sellToken.populateTransaction(0n, tokenAddress, amount, 0n));
|
|
142
|
+
// ✅ 规划多笔交易 nonce
|
|
143
|
+
const multiNoncePlan = await planMultiNonces({
|
|
144
|
+
buyer,
|
|
145
|
+
seller,
|
|
146
|
+
buyCount,
|
|
147
|
+
sellCount,
|
|
148
|
+
extractProfit,
|
|
149
|
+
needBribeTx,
|
|
150
|
+
sameAddress,
|
|
151
|
+
nonceManager
|
|
152
|
+
});
|
|
153
|
+
const [sellResult, buyUnsignedArray, sellUnsignedArray] = await Promise.all([
|
|
103
154
|
trySell('BSC', config.rpcUrl, tokenAddress, sellAmountWei),
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
getNoncesPromise
|
|
155
|
+
Promise.all(buyUnsignedPromises),
|
|
156
|
+
Promise.all(sellUnsignedPromises)
|
|
107
157
|
]);
|
|
108
|
-
const { buyerNonces, sellerNonces } = noncesResult;
|
|
109
158
|
const estimatedSellFunds = sellResult.funds;
|
|
110
159
|
const profitAmount = extractProfit ? (estimatedSellFunds * BigInt(profitRateBps)) / 10000n : 0n;
|
|
111
|
-
// 分配 nonces
|
|
112
|
-
let buyerNonce;
|
|
113
|
-
let sellerNonce;
|
|
114
|
-
let bribeNonce;
|
|
115
|
-
let profitNonce;
|
|
116
|
-
if (sameAddress) {
|
|
117
|
-
let idx = 0;
|
|
118
|
-
if (needBribeTx)
|
|
119
|
-
bribeNonce = sellerNonces[idx++];
|
|
120
|
-
buyerNonce = sellerNonces[idx++];
|
|
121
|
-
sellerNonce = sellerNonces[idx++];
|
|
122
|
-
if (extractProfit)
|
|
123
|
-
profitNonce = sellerNonces[idx];
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
buyerNonce = buyerNonces[0];
|
|
127
|
-
let idx = 0;
|
|
128
|
-
if (needBribeTx)
|
|
129
|
-
bribeNonce = sellerNonces[idx++];
|
|
130
|
-
sellerNonce = sellerNonces[idx++];
|
|
131
|
-
if (extractProfit)
|
|
132
|
-
profitNonce = sellerNonces[idx];
|
|
133
|
-
}
|
|
134
|
-
// ✅ 并行签名所有交易
|
|
135
|
-
const signPromises = [];
|
|
136
160
|
// ✅ 贿赂交易放在首位
|
|
137
161
|
let bribeTx = null;
|
|
138
|
-
if (needBribeTx && bribeNonce !== undefined) {
|
|
139
|
-
|
|
162
|
+
if (needBribeTx && multiNoncePlan.bribeNonce !== undefined) {
|
|
163
|
+
bribeTx = await seller.signTransaction({
|
|
140
164
|
to: BLOCKRAZOR_BUILDER_EOA,
|
|
141
165
|
value: bribeAmount,
|
|
142
|
-
nonce: bribeNonce,
|
|
166
|
+
nonce: multiNoncePlan.bribeNonce,
|
|
143
167
|
gasPrice,
|
|
144
168
|
gasLimit: 21000n,
|
|
145
169
|
chainId: chainIdNum,
|
|
146
170
|
type: txType
|
|
147
|
-
})
|
|
171
|
+
});
|
|
148
172
|
}
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
...
|
|
173
|
+
// ✅ 并行签名所有买入和卖出交易
|
|
174
|
+
const buySignPromises = buyUnsignedArray.map((unsigned, i) => buyer.signTransaction({
|
|
175
|
+
...unsigned,
|
|
152
176
|
from: buyer.address,
|
|
153
|
-
nonce:
|
|
177
|
+
nonce: multiNoncePlan.buyerNonces[i],
|
|
154
178
|
gasLimit: finalGasLimit,
|
|
155
179
|
gasPrice,
|
|
156
180
|
chainId: chainIdNum,
|
|
157
181
|
type: txType,
|
|
158
|
-
value:
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
...sellUnsigned,
|
|
182
|
+
value: buyAmountsWei[i]
|
|
183
|
+
}));
|
|
184
|
+
const sellSignPromises = sellUnsignedArray.map((unsigned, i) => seller.signTransaction({
|
|
185
|
+
...unsigned,
|
|
163
186
|
from: seller.address,
|
|
164
|
-
nonce:
|
|
187
|
+
nonce: multiNoncePlan.sellerNonces[i],
|
|
165
188
|
gasLimit: finalGasLimit,
|
|
166
189
|
gasPrice,
|
|
167
190
|
chainId: chainIdNum,
|
|
168
191
|
type: txType,
|
|
169
|
-
value: 0n
|
|
192
|
+
value: 0n
|
|
170
193
|
}));
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
bribeTx = signedTxs[idx++];
|
|
176
|
-
const signedBuy = signedTxs[idx++];
|
|
177
|
-
const signedSell = signedTxs[idx++];
|
|
194
|
+
const [signedBuys, signedSells] = await Promise.all([
|
|
195
|
+
Promise.all(buySignPromises),
|
|
196
|
+
Promise.all(sellSignPromises)
|
|
197
|
+
]);
|
|
178
198
|
nonceManager.clearTemp();
|
|
179
|
-
// ✅ 组装交易列表:贿赂 → 买入 → 卖出
|
|
199
|
+
// ✅ 组装交易列表:贿赂 → 买入(多笔) → 卖出(多笔)
|
|
180
200
|
const allTransactions = [];
|
|
181
201
|
if (bribeTx)
|
|
182
202
|
allTransactions.push(bribeTx);
|
|
183
|
-
allTransactions.push(
|
|
203
|
+
allTransactions.push(...signedBuys, ...signedSells);
|
|
184
204
|
// ✅ 利润多跳转账(强制 2 跳中转)
|
|
185
205
|
let profitHopWallets;
|
|
186
|
-
if (extractProfit && profitAmount > 0n && profitNonce !== undefined) {
|
|
206
|
+
if (extractProfit && profitAmount > 0n && multiNoncePlan.profitNonce !== undefined) {
|
|
187
207
|
const profitHopResult = await buildProfitHopTransactions({
|
|
188
208
|
provider,
|
|
189
209
|
payerWallet: seller,
|
|
@@ -193,7 +213,7 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
193
213
|
gasPrice,
|
|
194
214
|
chainId: chainIdNum,
|
|
195
215
|
txType,
|
|
196
|
-
startNonce: profitNonce
|
|
216
|
+
startNonce: multiNoncePlan.profitNonce
|
|
197
217
|
});
|
|
198
218
|
allTransactions.push(...profitHopResult.signedTransactions);
|
|
199
219
|
profitHopWallets = profitHopResult.hopWallets; // ✅ 收集利润多跳钱包
|
|
@@ -206,7 +226,42 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
206
226
|
sellerAddress: seller.address,
|
|
207
227
|
buyAmount: ethers.formatEther(buyerFundsWei),
|
|
208
228
|
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
209
|
-
profitAmount: extractProfit ? ethers.formatEther(profitAmount) : undefined
|
|
229
|
+
profitAmount: extractProfit ? ethers.formatEther(profitAmount) : undefined,
|
|
230
|
+
// ✅ 返回多笔买卖信息
|
|
231
|
+
buyCount,
|
|
232
|
+
sellCount
|
|
210
233
|
}
|
|
211
234
|
};
|
|
212
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* ✅ 规划多笔交易 nonce
|
|
238
|
+
* 交易顺序:贿赂 → 买入(多笔) → 卖出(多笔) → 利润
|
|
239
|
+
*/
|
|
240
|
+
async function planMultiNonces({ buyer, seller, buyCount, sellCount, extractProfit, needBribeTx, sameAddress, nonceManager }) {
|
|
241
|
+
const profitNonceCount = extractProfit ? 1 : 0;
|
|
242
|
+
if (sameAddress) {
|
|
243
|
+
const bribeTxCount = needBribeTx ? 1 : 0;
|
|
244
|
+
const totalTxCount = bribeTxCount + buyCount + sellCount + profitNonceCount;
|
|
245
|
+
const nonces = await nonceManager.getNextNonceBatch(buyer, totalTxCount);
|
|
246
|
+
let idx = 0;
|
|
247
|
+
const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
|
|
248
|
+
const buyerNonces = nonces.slice(idx, idx + buyCount);
|
|
249
|
+
idx += buyCount;
|
|
250
|
+
const sellerNonces = nonces.slice(idx, idx + sellCount);
|
|
251
|
+
idx += sellCount;
|
|
252
|
+
const profitNonce = extractProfit ? nonces[idx] : undefined;
|
|
253
|
+
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
254
|
+
}
|
|
255
|
+
const bribeTxCount = needBribeTx ? 1 : 0;
|
|
256
|
+
const sellerTxCount = bribeTxCount + sellCount + profitNonceCount;
|
|
257
|
+
const [buyerNonces, sellerNoncesAll] = await Promise.all([
|
|
258
|
+
nonceManager.getNextNonceBatch(buyer, buyCount),
|
|
259
|
+
nonceManager.getNextNonceBatch(seller, sellerTxCount)
|
|
260
|
+
]);
|
|
261
|
+
let idx = 0;
|
|
262
|
+
const bribeNonce = needBribeTx ? sellerNoncesAll[idx++] : undefined;
|
|
263
|
+
const sellerNonces = sellerNoncesAll.slice(idx, idx + sellCount);
|
|
264
|
+
idx += sellCount;
|
|
265
|
+
const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
|
|
266
|
+
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
267
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -30,6 +30,8 @@ export interface FlapBundleBuyFirstSignParams {
|
|
|
30
30
|
config: FlapBuyFirstSignConfig;
|
|
31
31
|
quoteToken?: string;
|
|
32
32
|
quoteTokenDecimals?: number;
|
|
33
|
+
buyCount?: number;
|
|
34
|
+
sellCount?: number;
|
|
33
35
|
}
|
|
34
36
|
export interface FlapBundleBuyFirstParams {
|
|
35
37
|
chain: FlapChain;
|
|
@@ -50,6 +52,8 @@ export type FlapBuyFirstResult = {
|
|
|
50
52
|
buyAmount: string;
|
|
51
53
|
sellAmount: string;
|
|
52
54
|
profitAmount?: string;
|
|
55
|
+
buyCount?: number;
|
|
56
|
+
sellCount?: number;
|
|
53
57
|
};
|
|
54
58
|
};
|
|
55
59
|
export declare function flapBundleBuyFirstMerkle(params: FlapBundleBuyFirstSignParams): Promise<FlapBuyFirstResult>;
|