four-flap-meme-sdk 4.3.10 → 4.3.11
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.
|
@@ -13,6 +13,7 @@ import { trySell } from '../../../domains/four/contracts/tm.js';
|
|
|
13
13
|
import { createChainContext, createWallets } from '../core-helpers.js';
|
|
14
14
|
import { TokenManagerHelper3 as Helper3Abi } from '../../../abis/contracts/index.js';
|
|
15
15
|
import { TM2_ABI, MULTICALL3_ADDRESS } from './constants.js';
|
|
16
|
+
import { prefixSumAmounts, sequentialFundsFromPrefixQuotes } from '../sequential-sell-quote.js';
|
|
16
17
|
export async function batchSellWithBundleMerkle(params) {
|
|
17
18
|
const { privateKeys, sellAmounts, tokenAddress, minOutputAmounts, config } = params;
|
|
18
19
|
if (privateKeys.length === 0 || sellAmounts.length !== privateKeys.length) {
|
|
@@ -54,7 +55,7 @@ async function executeSellFlow(options) {
|
|
|
54
55
|
// ✅ 获取贿赂金额
|
|
55
56
|
const bribeAmount = getBribeAmount(config);
|
|
56
57
|
const needBribeTx = bribeAmount > 0n;
|
|
57
|
-
//
|
|
58
|
+
// 报价 / 余额 / nonce 并行;多笔卖出的 trySell 按前缀顺序拆,避免同一池子被加总多次。
|
|
58
59
|
const [sellOutputs, , nonces] = await Promise.all([
|
|
59
60
|
resolveSellOutputs({
|
|
60
61
|
minOutputAmounts,
|
|
@@ -114,48 +115,42 @@ async function executeSellFlow(options) {
|
|
|
114
115
|
});
|
|
115
116
|
return { signedTxs, profitHopWallets }; // ✅ 返回利润多跳钱包
|
|
116
117
|
}
|
|
117
|
-
/**
|
|
118
|
-
* ✅ 使用 Multicall3 批量获取卖出报价(单次 RPC)
|
|
119
|
-
* 比 Promise.all 更高效,N 个报价只需 1 次网络请求
|
|
120
|
-
*/
|
|
121
118
|
async function resolveSellOutputs(params) {
|
|
122
119
|
const { minOutputAmounts, sellers, amountsWei, rpcUrl, tokenAddress } = params;
|
|
123
|
-
// 如果已提供 minOutputAmounts,直接使用,跳过报价查询
|
|
124
120
|
if (minOutputAmounts && minOutputAmounts.length === sellers.length) {
|
|
125
121
|
const minOuts = minOutputAmounts.map((m) => (typeof m === 'string' ? ethers.parseEther(m) : m));
|
|
126
122
|
const quotedOutputs = minOuts.map((value) => reverseSlippageBps(value));
|
|
127
123
|
return { minOuts, quotedOutputs };
|
|
128
124
|
}
|
|
129
|
-
|
|
130
|
-
if (
|
|
125
|
+
const prefixes = prefixSumAmounts(amountsWei);
|
|
126
|
+
if (prefixes.length === 1) {
|
|
127
|
+
const only = prefixes[0];
|
|
128
|
+
if (only === undefined)
|
|
129
|
+
return { minOuts: [0n], quotedOutputs: [0n] };
|
|
131
130
|
try {
|
|
132
|
-
const result = await trySell('BSC', rpcUrl, tokenAddress,
|
|
133
|
-
const quotedOutputs = [result.funds];
|
|
134
|
-
|
|
135
|
-
return { minOuts, quotedOutputs };
|
|
131
|
+
const result = await trySell('BSC', rpcUrl, tokenAddress, only);
|
|
132
|
+
const quotedOutputs = sequentialFundsFromPrefixQuotes([result.funds]);
|
|
133
|
+
return { minOuts: quotedOutputs.map((q) => applySlippageBps(q)), quotedOutputs };
|
|
136
134
|
}
|
|
137
135
|
catch {
|
|
138
136
|
return { minOuts: [0n], quotedOutputs: [0n] };
|
|
139
137
|
}
|
|
140
138
|
}
|
|
141
|
-
// ✅ 使用 Multicall3 批量获取报价
|
|
142
139
|
const provider = createSignProvider(rpcUrl, CHAINS.BSC.chainId, 'BSC');
|
|
143
140
|
const helper3Address = ADDRESSES.BSC.TokenManagerHelper3;
|
|
144
141
|
const helper3Iface = new Interface(Helper3Abi);
|
|
145
142
|
const multicall = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
|
|
146
|
-
|
|
147
|
-
const calls = amountsWei.map((amount) => ({
|
|
143
|
+
const calls = prefixes.map((amount) => ({
|
|
148
144
|
target: helper3Address,
|
|
149
|
-
allowFailure: true,
|
|
145
|
+
allowFailure: true,
|
|
150
146
|
callData: helper3Iface.encodeFunctionData('trySell', [tokenAddress, amount]),
|
|
151
147
|
}));
|
|
152
148
|
try {
|
|
153
149
|
const results = await multicall.aggregate3.staticCall(calls);
|
|
154
|
-
const
|
|
150
|
+
const prefixFunds = results.map((r) => {
|
|
155
151
|
if (r.success && r.returnData && r.returnData !== '0x') {
|
|
156
152
|
try {
|
|
157
153
|
const decoded = helper3Iface.decodeFunctionResult('trySell', r.returnData);
|
|
158
|
-
// trySell 返回 (tokenManager, quote, funds, fee),我们需要 funds
|
|
159
154
|
return BigInt(decoded.funds);
|
|
160
155
|
}
|
|
161
156
|
catch {
|
|
@@ -164,12 +159,11 @@ async function resolveSellOutputs(params) {
|
|
|
164
159
|
}
|
|
165
160
|
return 0n;
|
|
166
161
|
});
|
|
167
|
-
const
|
|
168
|
-
return { minOuts, quotedOutputs };
|
|
162
|
+
const quotedOutputs = sequentialFundsFromPrefixQuotes(prefixFunds);
|
|
163
|
+
return { minOuts: quotedOutputs.map((q) => applySlippageBps(q)), quotedOutputs };
|
|
169
164
|
}
|
|
170
165
|
catch {
|
|
171
|
-
|
|
172
|
-
const quotedOutputs = await Promise.all(amountsWei.map(async (amount) => {
|
|
166
|
+
const prefixFunds = await Promise.all(prefixes.map(async (amount) => {
|
|
173
167
|
try {
|
|
174
168
|
const result = await trySell('BSC', rpcUrl, tokenAddress, amount);
|
|
175
169
|
return result.funds;
|
|
@@ -178,8 +172,8 @@ async function resolveSellOutputs(params) {
|
|
|
178
172
|
return 0n;
|
|
179
173
|
}
|
|
180
174
|
}));
|
|
181
|
-
const
|
|
182
|
-
return { minOuts, quotedOutputs };
|
|
175
|
+
const quotedOutputs = sequentialFundsFromPrefixQuotes(prefixFunds);
|
|
176
|
+
return { minOuts: quotedOutputs.map((q) => applySlippageBps(q)), quotedOutputs };
|
|
183
177
|
}
|
|
184
178
|
}
|
|
185
179
|
async function ensureSellerBalances(params) {
|
|
@@ -5,6 +5,7 @@ import { ethers, Wallet } from 'ethers';
|
|
|
5
5
|
import { getGasLimit } from '../../../core/gas/bundle-gas.js';
|
|
6
6
|
import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
|
|
7
7
|
import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA, CHAINS, applySlippageBps, reverseSlippageBps } from '../../../core/constants/index.js';
|
|
8
|
+
import { prefixSumAmounts, sequentialFundsFromPrefixQuotes } from '../sequential-sell-quote.js';
|
|
8
9
|
import { TM2_ABI } from '../../../abis/common.js';
|
|
9
10
|
import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, getProfitRecipient, getBribeAmount, } from '../../config-helpers.js';
|
|
10
11
|
import { trySell } from '../../../domains/four/contracts/tm.js';
|
|
@@ -114,24 +115,26 @@ export async function fourBatchPrivateSellMerkle(params) {
|
|
|
114
115
|
const wallets = privateKeys.map((k) => new Wallet(k, provider));
|
|
115
116
|
const amountsWei = amounts.map((a) => ethers.parseUnits(a, 18));
|
|
116
117
|
const rpcUrl = config.rpcUrl;
|
|
117
|
-
// ✅ 优化:并行获取 gasPrice 和 quotedOutputs(已移除授权和余额检查)
|
|
118
118
|
const [gasPrice, quotedOutputs] = await Promise.all([
|
|
119
119
|
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
120
|
-
// 获取预期收益(用于计算利润)
|
|
121
120
|
minFundsEach !== undefined
|
|
122
121
|
? Promise.resolve((() => {
|
|
123
122
|
const minOutWei = typeof minFundsEach === 'string' ? ethers.parseEther(minFundsEach) : minFundsEach;
|
|
124
123
|
return new Array(wallets.length).fill(reverseSlippageBps(minOutWei));
|
|
125
124
|
})())
|
|
126
|
-
:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
125
|
+
: (async () => {
|
|
126
|
+
const prefixes = prefixSumAmounts(amountsWei);
|
|
127
|
+
const prefixFunds = await Promise.all(prefixes.map(async (amount) => {
|
|
128
|
+
try {
|
|
129
|
+
const result = await trySell('BSC', rpcUrl, tokenAddress, amount);
|
|
130
|
+
return result.funds;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return 0n;
|
|
134
|
+
}
|
|
135
|
+
}));
|
|
136
|
+
return sequentialFundsFromPrefixQuotes(prefixFunds);
|
|
137
|
+
})(),
|
|
135
138
|
]);
|
|
136
139
|
// 报价 × (1 - 100bps)
|
|
137
140
|
const minOuts = quotedOutputs.map((q) => applySlippageBps(q));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内盘多笔卖出在同一 bundle 里是顺序成交的。
|
|
3
|
+
* Helper3.trySell(amount) 只反映「当前池子一次性卖掉 amount」,
|
|
4
|
+
* 五笔并行报价会把同一份 BNB 加五次,后面几笔 minFunds 必然在模拟里回滚。
|
|
5
|
+
* 用前缀数量报价:第 i 笔应得 ≈ trySell(sum 0..i) - trySell(sum 0..i-1)。
|
|
6
|
+
*/
|
|
7
|
+
export declare function prefixSumAmounts(amounts: bigint[]): bigint[];
|
|
8
|
+
export declare function sequentialFundsFromPrefixQuotes(prefixFunds: bigint[]): bigint[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内盘多笔卖出在同一 bundle 里是顺序成交的。
|
|
3
|
+
* Helper3.trySell(amount) 只反映「当前池子一次性卖掉 amount」,
|
|
4
|
+
* 五笔并行报价会把同一份 BNB 加五次,后面几笔 minFunds 必然在模拟里回滚。
|
|
5
|
+
* 用前缀数量报价:第 i 笔应得 ≈ trySell(sum 0..i) - trySell(sum 0..i-1)。
|
|
6
|
+
*/
|
|
7
|
+
export function prefixSumAmounts(amounts) {
|
|
8
|
+
const prefixes = [];
|
|
9
|
+
let sum = 0n;
|
|
10
|
+
for (const amount of amounts) {
|
|
11
|
+
sum += amount > 0n ? amount : 0n;
|
|
12
|
+
prefixes.push(sum);
|
|
13
|
+
}
|
|
14
|
+
return prefixes;
|
|
15
|
+
}
|
|
16
|
+
export function sequentialFundsFromPrefixQuotes(prefixFunds) {
|
|
17
|
+
const sequential = [];
|
|
18
|
+
let prev = 0n;
|
|
19
|
+
for (const funds of prefixFunds) {
|
|
20
|
+
const quoted = funds > 0n ? funds : 0n;
|
|
21
|
+
if (quoted <= prev) {
|
|
22
|
+
sequential.push(0n);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
sequential.push(quoted - prev);
|
|
26
|
+
prev = quoted;
|
|
27
|
+
}
|
|
28
|
+
return sequential;
|
|
29
|
+
}
|