four-flap-meme-sdk 1.2.71 → 1.2.73
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.
|
@@ -2,6 +2,7 @@ import { ethers, Wallet } from 'ethers';
|
|
|
2
2
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
3
3
|
import { ADDRESSES } from '../../utils/constants.js';
|
|
4
4
|
import { FourClient, buildLoginMessage } from '../../clients/four.js';
|
|
5
|
+
import axios from 'axios';
|
|
5
6
|
import { getErrorMessage, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, getProfitRecipient } from './config.js';
|
|
6
7
|
import { batchCheckAllowances } from '../../utils/erc20.js';
|
|
7
8
|
import { trySell } from '../tm.js';
|
|
@@ -142,23 +143,41 @@ export async function createTokenWithBundleBuyMerkle(params) {
|
|
|
142
143
|
console.log(`✅ 总共生成了 ${signedTxs.length} 个交易签名 (1创建 + ${buyerKeys.length}买入 + 利润)`);
|
|
143
144
|
// ✅ 内部提交到 48.club Bundle
|
|
144
145
|
console.log('📡 开始提交到 48.club Bundle...');
|
|
145
|
-
|
|
146
|
+
let bundleUuid;
|
|
146
147
|
if (config.customSubmitFn) {
|
|
147
|
-
//
|
|
148
|
-
|
|
148
|
+
// ✅ 方式 1:使用自定义提交函数(通过服务器代理)
|
|
149
|
+
bundleUuid = await config.customSubmitFn(signedTxs);
|
|
149
150
|
console.log(`✅ Bundle 提交成功!UUID: ${bundleUuid}`);
|
|
150
|
-
// 等待交易上链并解析代币地址
|
|
151
|
-
const tokenAddress = await waitAndParseTokenAddress(provider, signedTxs[0], bundleUuid);
|
|
152
|
-
return {
|
|
153
|
-
signedTransactions: signedTxs,
|
|
154
|
-
tokenAddress,
|
|
155
|
-
metadata,
|
|
156
|
-
bundleUuid
|
|
157
|
-
};
|
|
158
151
|
}
|
|
159
152
|
else {
|
|
160
|
-
|
|
153
|
+
// ✅ 方式 2:使用免费的 eth_sendBundle(不需要 48SP 签名)
|
|
154
|
+
console.log('📡 使用免费 Bundle 提交...');
|
|
155
|
+
const endpoint = config.club48Endpoint || 'https://puissant-bsc.48.club';
|
|
156
|
+
const bundleParams = {
|
|
157
|
+
txs: signedTxs,
|
|
158
|
+
maxTimestamp: Math.floor(Date.now() / 1000) + 300, // 5 分钟有效期
|
|
159
|
+
revertingTxHashes: []
|
|
160
|
+
};
|
|
161
|
+
const response = await axios.post(endpoint, {
|
|
162
|
+
jsonrpc: "2.0",
|
|
163
|
+
id: 1,
|
|
164
|
+
method: "eth_sendBundle",
|
|
165
|
+
params: [bundleParams]
|
|
166
|
+
});
|
|
167
|
+
if (response.data.error) {
|
|
168
|
+
throw new Error(`48.club bundle 提交失败: ${response.data.error.message}`);
|
|
169
|
+
}
|
|
170
|
+
bundleUuid = response.data.result;
|
|
171
|
+
console.log(`✅ 免费 Bundle 提交成功!UUID: ${bundleUuid}`);
|
|
161
172
|
}
|
|
173
|
+
// 等待交易上链并解析代币地址
|
|
174
|
+
const tokenAddress = await waitAndParseTokenAddress(provider, signedTxs[0], bundleUuid);
|
|
175
|
+
return {
|
|
176
|
+
signedTransactions: signedTxs,
|
|
177
|
+
tokenAddress,
|
|
178
|
+
metadata,
|
|
179
|
+
bundleUuid
|
|
180
|
+
};
|
|
162
181
|
}
|
|
163
182
|
/**
|
|
164
183
|
* 等待交易上链并解析代币地址
|
|
@@ -12,6 +12,9 @@ export type FourSignConfig = {
|
|
|
12
12
|
prefer21000ForNative?: boolean;
|
|
13
13
|
checkBnbForErc20NoHop?: boolean;
|
|
14
14
|
customSubmitFn?: (signedTxs: string[]) => Promise<string>;
|
|
15
|
+
spPrivateKey?: string;
|
|
16
|
+
spMode?: 'none' | 'timestampPersonalSign' | 'concatTxHash' | 'rawTimestamp';
|
|
17
|
+
spVMode?: '27_28' | '0_1';
|
|
15
18
|
};
|
|
16
19
|
export type FourBundleMerkleConfig = {
|
|
17
20
|
apiKey: string;
|
|
@@ -160,9 +160,17 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
160
160
|
const nonceManager = new NonceManager(provider);
|
|
161
161
|
for (let i = 0; i < recipients.length; i++) {
|
|
162
162
|
const finalRecipient = recipients[i];
|
|
163
|
-
const
|
|
163
|
+
const originalAmountWei = isNative
|
|
164
164
|
? ethers.parseEther(normalizedAmounts[i])
|
|
165
165
|
: ethers.parseUnits(normalizedAmounts[i], decimals);
|
|
166
|
+
// ✅ 计算利润并扣除
|
|
167
|
+
totalAmountBeforeProfit += originalAmountWei;
|
|
168
|
+
let amountWei = originalAmountWei;
|
|
169
|
+
if (extractProfit) {
|
|
170
|
+
const { profit, remaining } = calculateProfit(originalAmountWei, config);
|
|
171
|
+
amountWei = remaining;
|
|
172
|
+
totalProfit += profit;
|
|
173
|
+
}
|
|
166
174
|
const hopChain = preparedHops[i];
|
|
167
175
|
if (hopChain.length === 0) {
|
|
168
176
|
// 该接收者无跳转,直接转账
|
|
@@ -257,6 +265,36 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
257
265
|
}
|
|
258
266
|
}
|
|
259
267
|
}
|
|
268
|
+
// ✅ 添加利润转账(多跳模式)
|
|
269
|
+
if (extractProfit && totalProfit > 0n) {
|
|
270
|
+
const profitNonce = await nonceManager.getNextNonce(mainWallet);
|
|
271
|
+
if (isNative) {
|
|
272
|
+
const profitTx = await mainWallet.signTransaction({
|
|
273
|
+
to: getProfitRecipient(),
|
|
274
|
+
value: totalProfit,
|
|
275
|
+
nonce: profitNonce,
|
|
276
|
+
gasPrice,
|
|
277
|
+
gasLimit: 21000n,
|
|
278
|
+
chainId: chainIdNum,
|
|
279
|
+
type: txType
|
|
280
|
+
});
|
|
281
|
+
signedTxs.push(profitTx);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
const profitData = iface.encodeFunctionData('transfer', [getProfitRecipient(), totalProfit]);
|
|
285
|
+
const profitTx = await mainWallet.signTransaction({
|
|
286
|
+
to: tokenAddress,
|
|
287
|
+
data: profitData,
|
|
288
|
+
value: 0n,
|
|
289
|
+
nonce: profitNonce,
|
|
290
|
+
gasPrice,
|
|
291
|
+
gasLimit: 60000n,
|
|
292
|
+
chainId: chainIdNum,
|
|
293
|
+
type: txType
|
|
294
|
+
});
|
|
295
|
+
signedTxs.push(profitTx);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
260
298
|
}
|
|
261
299
|
// ✅ 简化返回:只返回签名交易、多跳钱包和元数据
|
|
262
300
|
return {
|
|
@@ -641,6 +679,8 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
641
679
|
_batchGetBalances(provider, sourceAddresses, tokenAddress),
|
|
642
680
|
isNative ? Promise.resolve([]) : _batchGetBalances(provider, sourceAddresses)
|
|
643
681
|
]);
|
|
682
|
+
// ✅ 用于记录每个钱包的归集金额(用于计算利润)
|
|
683
|
+
const sweepAmounts = new Array(sourceWallets.length).fill(0n);
|
|
644
684
|
// 处理无跳转的地址(批量)
|
|
645
685
|
if (withoutHopIndexes.length > 0) {
|
|
646
686
|
for (let idx = 0; idx < withoutHopIndexes.length; idx++) {
|
|
@@ -676,6 +716,8 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
676
716
|
toSend = amt;
|
|
677
717
|
}
|
|
678
718
|
if (toSend > 0n) {
|
|
719
|
+
sweepAmounts[i] = toSend; // ✅ 记录归集金额
|
|
720
|
+
totalAmountBeforeProfit += toSend;
|
|
679
721
|
const nonce = await nonceManager.getNextNonce(sourceWallet);
|
|
680
722
|
const tx = await sourceWallet.signTransaction({
|
|
681
723
|
to: target,
|
|
@@ -711,6 +753,8 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
711
753
|
toSend = ethers.parseUnits(amountStrForI, decimals);
|
|
712
754
|
}
|
|
713
755
|
if (toSend > 0n && (!skipIfInsufficient || bal >= toSend)) {
|
|
756
|
+
sweepAmounts[i] = toSend; // ✅ 记录归集金额
|
|
757
|
+
totalAmountBeforeProfit += toSend;
|
|
714
758
|
const nonce = await nonceManager.getNextNonce(sourceWallet);
|
|
715
759
|
const data = iface.encodeFunctionData('transfer', [target, toSend]);
|
|
716
760
|
const tx = await sourceWallet.signTransaction({
|
|
@@ -794,6 +838,9 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
794
838
|
}
|
|
795
839
|
if (toSend <= 0n)
|
|
796
840
|
continue;
|
|
841
|
+
// ✅ 记录归集金额
|
|
842
|
+
sweepAmounts[i] = toSend;
|
|
843
|
+
totalAmountBeforeProfit += toSend;
|
|
797
844
|
// 构建跳转链: 子钱包 -> 中转1 -> 中转2 -> ... -> 目标地址
|
|
798
845
|
const fullChain = [sourceWallet, ...hopChain.map(pk => new Wallet(pk, provider))];
|
|
799
846
|
const addresses = [...fullChain.map(w => w.address), target];
|
|
@@ -860,6 +907,56 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
860
907
|
}
|
|
861
908
|
}
|
|
862
909
|
}
|
|
910
|
+
// ✅ 多跳模式:计算利润并添加利润转账
|
|
911
|
+
if (extractProfit && totalAmountBeforeProfit > 0n) {
|
|
912
|
+
// 找出归集金额最大的钱包作为支付者
|
|
913
|
+
let maxSweepIndex = -1;
|
|
914
|
+
let maxSweepAmount = 0n;
|
|
915
|
+
for (let i = 0; i < sweepAmounts.length; i++) {
|
|
916
|
+
if (sweepAmounts[i] > maxSweepAmount) {
|
|
917
|
+
maxSweepAmount = sweepAmounts[i];
|
|
918
|
+
maxSweepIndex = i;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
// 计算总利润
|
|
922
|
+
for (let i = 0; i < sweepAmounts.length; i++) {
|
|
923
|
+
if (sweepAmounts[i] > 0n) {
|
|
924
|
+
const { profit } = calculateProfit(sweepAmounts[i], config);
|
|
925
|
+
totalProfit += profit;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
// 由归集金额最大的钱包支付利润
|
|
929
|
+
if (totalProfit > 0n && maxSweepIndex >= 0) {
|
|
930
|
+
const payerWallet = sourceWallets[maxSweepIndex];
|
|
931
|
+
const profitNonce = await nonceManager.getNextNonce(payerWallet);
|
|
932
|
+
if (isNative) {
|
|
933
|
+
const profitTx = await payerWallet.signTransaction({
|
|
934
|
+
to: getProfitRecipient(),
|
|
935
|
+
value: totalProfit,
|
|
936
|
+
nonce: profitNonce,
|
|
937
|
+
gasPrice,
|
|
938
|
+
gasLimit: 21000n,
|
|
939
|
+
chainId: chainIdNum,
|
|
940
|
+
type: txType
|
|
941
|
+
});
|
|
942
|
+
signedTxs.push(profitTx);
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
const profitData = iface.encodeFunctionData('transfer', [getProfitRecipient(), totalProfit]);
|
|
946
|
+
const profitTx = await payerWallet.signTransaction({
|
|
947
|
+
to: tokenAddress,
|
|
948
|
+
data: profitData,
|
|
949
|
+
value: 0n,
|
|
950
|
+
nonce: profitNonce,
|
|
951
|
+
gasPrice,
|
|
952
|
+
gasLimit: finalGasLimit,
|
|
953
|
+
chainId: chainIdNum,
|
|
954
|
+
type: txType
|
|
955
|
+
});
|
|
956
|
+
signedTxs.push(profitTx);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
863
960
|
}
|
|
864
961
|
// ✅ 简化返回:只返回签名交易、多跳钱包和元数据
|
|
865
962
|
return {
|
|
@@ -304,10 +304,20 @@ export async function createTokenWithBundleBuy(params) {
|
|
|
304
304
|
// ✅ 使用自定义提交函数(通过服务器代理)
|
|
305
305
|
bundleUuid = await config.customSubmitFn(signedTxs);
|
|
306
306
|
}
|
|
307
|
+
else if (config.spPrivateKey) {
|
|
308
|
+
// ✅ 使用 48SP 批量提交(支持浏览器直接调用)
|
|
309
|
+
console.log('📡 使用 48SP 批量提交...');
|
|
310
|
+
await sendBatchPrivateTransactions(signedTxs, config.spPrivateKey, config.club48Endpoint || 'https://puissant-bsc.48.club', {
|
|
311
|
+
spMode: config.spMode ?? 'timestampPersonalSign',
|
|
312
|
+
spVMode: config.spVMode
|
|
313
|
+
});
|
|
314
|
+
// 批量提交不返回 bundleUuid,使用第一笔交易的 hash 作为标识
|
|
315
|
+
bundleUuid = ethers.keccak256(signedTxs[0]);
|
|
316
|
+
}
|
|
307
317
|
else {
|
|
308
|
-
// ✅ 直接提交到 48.club(仅服务器端可用)
|
|
318
|
+
// ✅ 直接提交到 48.club Bundle(仅服务器端可用)
|
|
309
319
|
if (!club48) {
|
|
310
|
-
throw new Error('❌
|
|
320
|
+
throw new Error('❌ 浏览器环境必须提供 customSubmitFn 或 spPrivateKey 来提交 Bundle(避免 CORS 限制)');
|
|
311
321
|
}
|
|
312
322
|
bundleUuid = await club48.sendBundle({
|
|
313
323
|
txs: signedTxs,
|