four-flap-meme-sdk 1.4.81 → 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.
- package/dist/flap/portal-bundle-merkle/create-to-dex.d.ts +100 -0
- package/dist/flap/portal-bundle-merkle/create-to-dex.js +378 -0
- package/dist/flap/portal-bundle-merkle/curve-to-dex.d.ts +88 -0
- package/dist/flap/portal-bundle-merkle/curve-to-dex.js +424 -0
- package/dist/flap/portal-bundle-merkle/index.d.ts +2 -0
- package/dist/flap/portal-bundle-merkle/index.js +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/utils/bundle-helpers.d.ts +5 -1
- package/dist/utils/bundle-helpers.js +36 -6
- package/package.json +1 -1
|
@@ -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 报价工具(统一管理)
|
|
@@ -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
|
*/
|
|
@@ -373,13 +373,22 @@ export function decodeV3Path(path) {
|
|
|
373
373
|
*/
|
|
374
374
|
export const PROFIT_HOP_COUNT = 2;
|
|
375
375
|
/**
|
|
376
|
-
*
|
|
376
|
+
* ✅ 需要多跳转账的链(目前只有 BSC)
|
|
377
|
+
* 其他链直接转账到收益地址
|
|
378
|
+
*/
|
|
379
|
+
const CHAINS_REQUIRE_HOP = [56]; // BSC chainId
|
|
380
|
+
/**
|
|
381
|
+
* 生成利润转账交易
|
|
377
382
|
*
|
|
383
|
+
* BSC 链(chainId=56):使用多跳转账
|
|
378
384
|
* 流程(2 跳):
|
|
379
385
|
* 1. 支付者 → 中转1(gas + 利润 + 中转1的gas)
|
|
380
386
|
* 2. 中转1 → 中转2(gas + 利润)
|
|
381
387
|
* 3. 中转2 → 利润地址(利润)
|
|
382
388
|
*
|
|
389
|
+
* 其他链:直接转账到收益地址
|
|
390
|
+
* 流程:支付者 → 利润地址
|
|
391
|
+
*
|
|
383
392
|
* @param config 配置参数
|
|
384
393
|
* @returns 签名交易和中转钱包私钥
|
|
385
394
|
*/
|
|
@@ -391,8 +400,33 @@ export async function buildProfitHopTransactions(config) {
|
|
|
391
400
|
}
|
|
392
401
|
// 固定 gas limit(原生代币转账,预留少量缓冲)
|
|
393
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 链:使用多跳转账
|
|
394
428
|
const gasFeePerHop = nativeTransferGasLimit * gasPrice;
|
|
395
|
-
//
|
|
429
|
+
// 生成中转钱包(使用统一的 generateWallets 函数)
|
|
396
430
|
const hopWalletsInfo = generateWallets(hopCount);
|
|
397
431
|
const hopWallets = hopWalletsInfo.map(w => new Wallet(w.privateKey, provider));
|
|
398
432
|
// 计算每个中转钱包需要的金额(从后往前)
|
|
@@ -409,10 +443,6 @@ export async function buildProfitHopTransactions(config) {
|
|
|
409
443
|
hopAmounts.unshift(hopAmounts[0] + gasFeePerHop);
|
|
410
444
|
}
|
|
411
445
|
}
|
|
412
|
-
const signedTxs = [];
|
|
413
|
-
const nonceManager = new NonceManager(provider);
|
|
414
|
-
// 获取支付者的 nonce
|
|
415
|
-
const payerNonce = startNonce ?? await nonceManager.getNextNonce(payerWallet);
|
|
416
446
|
// 1. 支付者 → 第一个中转钱包(包含所有后续的 gas 和利润)
|
|
417
447
|
const payerTx = await payerWallet.signTransaction({
|
|
418
448
|
to: hopWallets[0].address,
|