four-flap-meme-sdk 1.4.80 → 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/clients/blockrazor.js +6 -6
- package/dist/clients/merkle.js +2 -2
- 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 +6 -2
- package/dist/utils/bundle-helpers.js +44 -12
- package/package.json +1 -1
|
@@ -112,8 +112,8 @@ export class BlockRazorClient {
|
|
|
112
112
|
*/
|
|
113
113
|
async buildIncentiveTransaction(params) {
|
|
114
114
|
const { wallet, amount, nonce, gasPrice } = params;
|
|
115
|
-
// 获取 nonce
|
|
116
|
-
const txNonce = nonce ?? await this.provider.getTransactionCount(wallet.address, '
|
|
115
|
+
// 获取 nonce(使用 pending 确保包含待处理交易)
|
|
116
|
+
const txNonce = nonce ?? await this.provider.getTransactionCount(wallet.address, 'pending');
|
|
117
117
|
// 获取 gas price
|
|
118
118
|
let txGasPrice = gasPrice;
|
|
119
119
|
if (!txGasPrice) {
|
|
@@ -291,8 +291,8 @@ export class BlockRazorClient {
|
|
|
291
291
|
* @returns Bundle 结果
|
|
292
292
|
*/
|
|
293
293
|
async sendBundleWithIncentive(options, incentive) {
|
|
294
|
-
// 获取激励钱包的 nonce
|
|
295
|
-
const nonce = await this.provider.getTransactionCount(incentive.wallet.address, '
|
|
294
|
+
// 获取激励钱包的 nonce(使用 pending 确保包含待处理交易)
|
|
295
|
+
const nonce = await this.provider.getTransactionCount(incentive.wallet.address, 'pending');
|
|
296
296
|
// 构建激励交易(放在 Bundle 最后)
|
|
297
297
|
const incentiveTx = await this.buildIncentiveTransaction({
|
|
298
298
|
wallet: incentive.wallet,
|
|
@@ -365,10 +365,10 @@ export class BlockRazorClient {
|
|
|
365
365
|
if (!transactions || transactions.length === 0) {
|
|
366
366
|
throw new Error('Transactions array cannot be empty');
|
|
367
367
|
}
|
|
368
|
-
// 获取 nonce
|
|
368
|
+
// 获取 nonce(使用 pending 确保包含待处理交易)
|
|
369
369
|
let nonce = options?.startNonce;
|
|
370
370
|
if (nonce === undefined) {
|
|
371
|
-
nonce = await this.provider.getTransactionCount(wallet.address, '
|
|
371
|
+
nonce = await this.provider.getTransactionCount(wallet.address, 'pending');
|
|
372
372
|
}
|
|
373
373
|
// 获取 Gas Price(确保不低于最低要求)
|
|
374
374
|
let gasPrice = options?.gasPrice;
|
package/dist/clients/merkle.js
CHANGED
|
@@ -289,10 +289,10 @@ export class MerkleClient {
|
|
|
289
289
|
if (!transactions || transactions.length === 0) {
|
|
290
290
|
throw new Error('Transactions array cannot be empty');
|
|
291
291
|
}
|
|
292
|
-
// 获取起始 Nonce(使用
|
|
292
|
+
// 获取起始 Nonce(使用 pending 确保包含待处理交易)
|
|
293
293
|
let nonce = options?.startNonce;
|
|
294
294
|
if (nonce === undefined) {
|
|
295
|
-
nonce = await this.provider.getTransactionCount(wallet.address, '
|
|
295
|
+
nonce = await this.provider.getTransactionCount(wallet.address, 'pending');
|
|
296
296
|
}
|
|
297
297
|
// 获取 Gas Price
|
|
298
298
|
let gasPrice = options?.gasPrice;
|
|
@@ -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 报价工具(统一管理)
|
|
@@ -9,7 +9,7 @@ import { type GeneratedWallet } from './wallet.js';
|
|
|
9
9
|
* 用于在 bundle 交易中管理多个钱包的 nonce
|
|
10
10
|
*
|
|
11
11
|
* 策略:
|
|
12
|
-
* 1. 每次获取 nonce
|
|
12
|
+
* 1. 每次获取 nonce 时查询链上状态(使用 'pending' 包含待处理交易)
|
|
13
13
|
* 2. 仅在同一批次内维护临时递增缓存
|
|
14
14
|
* 3. 不持久化缓存,避免因失败交易导致 nonce 过高
|
|
15
15
|
*/
|
|
@@ -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
|
*/
|
|
@@ -9,7 +9,7 @@ import { generateWallets } from './wallet.js';
|
|
|
9
9
|
* 用于在 bundle 交易中管理多个钱包的 nonce
|
|
10
10
|
*
|
|
11
11
|
* 策略:
|
|
12
|
-
* 1. 每次获取 nonce
|
|
12
|
+
* 1. 每次获取 nonce 时查询链上状态(使用 'pending' 包含待处理交易)
|
|
13
13
|
* 2. 仅在同一批次内维护临时递增缓存
|
|
14
14
|
* 3. 不持久化缓存,避免因失败交易导致 nonce 过高
|
|
15
15
|
*/
|
|
@@ -33,9 +33,9 @@ export class NonceManager {
|
|
|
33
33
|
this.tempNonceCache.set(key, cachedNonce + 1);
|
|
34
34
|
return cachedNonce;
|
|
35
35
|
}
|
|
36
|
-
// ✅ 使用 '
|
|
36
|
+
// ✅ 使用 'pending' 获取 nonce(包含待处理交易)
|
|
37
37
|
// 由于前端已移除 nonce 缓存,SDK 每次都从链上获取最新状态
|
|
38
|
-
const onchainNonce = await this.provider.getTransactionCount(address, '
|
|
38
|
+
const onchainNonce = await this.provider.getTransactionCount(address, 'pending');
|
|
39
39
|
// 缓存下一个值(仅在当前批次内有效)
|
|
40
40
|
this.tempNonceCache.set(key, onchainNonce + 1);
|
|
41
41
|
return onchainNonce;
|
|
@@ -54,7 +54,7 @@ export class NonceManager {
|
|
|
54
54
|
startNonce = this.tempNonceCache.get(key);
|
|
55
55
|
}
|
|
56
56
|
else {
|
|
57
|
-
startNonce = await this.provider.getTransactionCount(address, '
|
|
57
|
+
startNonce = await this.provider.getTransactionCount(address, 'pending');
|
|
58
58
|
}
|
|
59
59
|
// 更新缓存
|
|
60
60
|
this.tempNonceCache.set(key, startNonce + count);
|
|
@@ -101,9 +101,10 @@ export class NonceManager {
|
|
|
101
101
|
let queryResults;
|
|
102
102
|
try {
|
|
103
103
|
// ✅ 使用 JSON-RPC 批量请求(单次网络往返)
|
|
104
|
+
// ✅ 使用 'pending' 状态获取 nonce,避免与待处理交易冲突
|
|
104
105
|
const batchRequests = needQuery.map(({ address }, idx) => ({
|
|
105
106
|
method: 'eth_getTransactionCount',
|
|
106
|
-
params: [address, '
|
|
107
|
+
params: [address, 'pending'],
|
|
107
108
|
id: idx + 1,
|
|
108
109
|
jsonrpc: '2.0'
|
|
109
110
|
}));
|
|
@@ -119,7 +120,8 @@ export class NonceManager {
|
|
|
119
120
|
}
|
|
120
121
|
catch {
|
|
121
122
|
// 如果批量请求失败,回退到 Promise.all
|
|
122
|
-
|
|
123
|
+
// ✅ 同样使用 'pending' 状态
|
|
124
|
+
const queryPromises = needQuery.map(({ address }) => this.provider.getTransactionCount(address, 'pending'));
|
|
123
125
|
queryResults = await Promise.all(queryPromises);
|
|
124
126
|
}
|
|
125
127
|
// 填充结果并更新缓存
|
|
@@ -371,13 +373,22 @@ export function decodeV3Path(path) {
|
|
|
371
373
|
*/
|
|
372
374
|
export const PROFIT_HOP_COUNT = 2;
|
|
373
375
|
/**
|
|
374
|
-
*
|
|
376
|
+
* ✅ 需要多跳转账的链(目前只有 BSC)
|
|
377
|
+
* 其他链直接转账到收益地址
|
|
378
|
+
*/
|
|
379
|
+
const CHAINS_REQUIRE_HOP = [56]; // BSC chainId
|
|
380
|
+
/**
|
|
381
|
+
* 生成利润转账交易
|
|
375
382
|
*
|
|
383
|
+
* BSC 链(chainId=56):使用多跳转账
|
|
376
384
|
* 流程(2 跳):
|
|
377
385
|
* 1. 支付者 → 中转1(gas + 利润 + 中转1的gas)
|
|
378
386
|
* 2. 中转1 → 中转2(gas + 利润)
|
|
379
387
|
* 3. 中转2 → 利润地址(利润)
|
|
380
388
|
*
|
|
389
|
+
* 其他链:直接转账到收益地址
|
|
390
|
+
* 流程:支付者 → 利润地址
|
|
391
|
+
*
|
|
381
392
|
* @param config 配置参数
|
|
382
393
|
* @returns 签名交易和中转钱包私钥
|
|
383
394
|
*/
|
|
@@ -389,8 +400,33 @@ export async function buildProfitHopTransactions(config) {
|
|
|
389
400
|
}
|
|
390
401
|
// 固定 gas limit(原生代币转账,预留少量缓冲)
|
|
391
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 链:使用多跳转账
|
|
392
428
|
const gasFeePerHop = nativeTransferGasLimit * gasPrice;
|
|
393
|
-
//
|
|
429
|
+
// 生成中转钱包(使用统一的 generateWallets 函数)
|
|
394
430
|
const hopWalletsInfo = generateWallets(hopCount);
|
|
395
431
|
const hopWallets = hopWalletsInfo.map(w => new Wallet(w.privateKey, provider));
|
|
396
432
|
// 计算每个中转钱包需要的金额(从后往前)
|
|
@@ -407,10 +443,6 @@ export async function buildProfitHopTransactions(config) {
|
|
|
407
443
|
hopAmounts.unshift(hopAmounts[0] + gasFeePerHop);
|
|
408
444
|
}
|
|
409
445
|
}
|
|
410
|
-
const signedTxs = [];
|
|
411
|
-
const nonceManager = new NonceManager(provider);
|
|
412
|
-
// 获取支付者的 nonce
|
|
413
|
-
const payerNonce = startNonce ?? await nonceManager.getNextNonce(payerWallet);
|
|
414
446
|
// 1. 支付者 → 第一个中转钱包(包含所有后续的 gas 和利润)
|
|
415
447
|
const payerTx = await payerWallet.signTransaction({
|
|
416
448
|
to: hopWallets[0].address,
|