four-flap-meme-sdk 1.3.80 → 1.3.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contracts/tm-bundle-merkle/approve-tokenmanager.d.ts +1 -0
- package/dist/contracts/tm-bundle-merkle/approve-tokenmanager.js +30 -28
- package/dist/contracts/tm-bundle-merkle/internal.d.ts +9 -1
- package/dist/contracts/tm-bundle-merkle/internal.js +53 -11
- package/dist/contracts/tm-bundle-merkle/private.d.ts +4 -0
- package/dist/contracts/tm-bundle-merkle/private.js +195 -183
- package/dist/contracts/tm-bundle-merkle/submit.d.ts +2 -2
- package/dist/contracts/tm-bundle-merkle/submit.js +18 -20
- package/dist/contracts/tm-bundle.js +224 -113
- package/dist/flap/portal-bundle-merkle/core.js +3 -3
- package/dist/flap/portal-bundle-merkle/private.js +87 -107
- package/dist/utils/airdrop-sweep.js +86 -76
- package/dist/utils/erc20.d.ts +1 -0
- package/dist/utils/erc20.js +22 -20
- package/dist/utils/lp-inspect.js +77 -65
- package/dist/utils/stealth-transfer.js +34 -37
- package/dist/utils/swap-helpers.js +8 -4
- package/package.json +1 -1
package/dist/utils/lp-inspect.js
CHANGED
|
@@ -103,18 +103,15 @@ export async function getFactoryFromRouter(routerAddress, rpcUrl, routerType = '
|
|
|
103
103
|
else {
|
|
104
104
|
// V3 SwapRouter02 可能同时有 V2 和 V3 Factory
|
|
105
105
|
const router = new Contract(routerAddress, V3_ROUTER_ABI, provider);
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
result.v2Factory =
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
}
|
|
106
|
+
// ✅ 并行获取 V3 和 V2 Factory
|
|
107
|
+
const [v3Factory, v2Factory] = await Promise.all([
|
|
108
|
+
router.factory().catch(() => undefined),
|
|
109
|
+
router.factoryV2().catch(() => undefined)
|
|
110
|
+
]);
|
|
111
|
+
if (v3Factory)
|
|
112
|
+
result.v3Factory = v3Factory;
|
|
113
|
+
if (v2Factory)
|
|
114
|
+
result.v2Factory = v2Factory;
|
|
118
115
|
}
|
|
119
116
|
}
|
|
120
117
|
catch (error) {
|
|
@@ -590,35 +587,42 @@ async function queryV2Pairs(token, factoryAddress, quoteTokens, provider, multic
|
|
|
590
587
|
return results;
|
|
591
588
|
}
|
|
592
589
|
async function queryV2PairsFallback(token, factoryAddress, quoteTokens, provider, tokenDecimals, shouldFormat) {
|
|
593
|
-
const results = [];
|
|
594
590
|
const factory = new Contract(factoryAddress, I_UNIV2_FACTORY_ABI, provider);
|
|
595
|
-
|
|
591
|
+
// ✅ 并行查询所有 quoteToken 的交易对
|
|
592
|
+
const pairResults = await Promise.all(quoteTokens.map(async (qt) => {
|
|
596
593
|
try {
|
|
597
594
|
const pairAddress = await factory.getPair(token, qt.address);
|
|
598
|
-
if (pairAddress
|
|
599
|
-
|
|
600
|
-
const [token0, token1] = await Promise.all([pair.token0(), pair.token1()]);
|
|
601
|
-
const [r0, r1] = await pair.getReserves();
|
|
602
|
-
const isToken0 = token0.toLowerCase() === token.toLowerCase();
|
|
603
|
-
const reserveToken = isToken0 ? r0 : r1;
|
|
604
|
-
const reserveQuote = isToken0 ? r1 : r0;
|
|
605
|
-
results.push({
|
|
606
|
-
quoteToken: qt.address,
|
|
607
|
-
quoteSymbol: qt.symbol,
|
|
608
|
-
quoteDecimals: qt.decimals,
|
|
609
|
-
pairAddress,
|
|
610
|
-
reserveToken: formatBalance(reserveToken, shouldFormat, tokenDecimals),
|
|
611
|
-
reserveQuote: formatBalance(reserveQuote, shouldFormat, qt.decimals),
|
|
612
|
-
reserveQuoteRaw: reserveQuote,
|
|
613
|
-
reserveTokenRaw: reserveToken, // ✅ 新增
|
|
614
|
-
});
|
|
595
|
+
if (!pairAddress || pairAddress.toLowerCase() === ZERO_ADDRESS) {
|
|
596
|
+
return null;
|
|
615
597
|
}
|
|
598
|
+
const pair = new Contract(pairAddress, I_UNIV2_PAIR_ABI, provider);
|
|
599
|
+
// ✅ 并行获取 token0、token1 和 reserves
|
|
600
|
+
const [token0, token1, reserves] = await Promise.all([
|
|
601
|
+
pair.token0(),
|
|
602
|
+
pair.token1(),
|
|
603
|
+
pair.getReserves()
|
|
604
|
+
]);
|
|
605
|
+
const [r0, r1] = reserves;
|
|
606
|
+
const isToken0 = token0.toLowerCase() === token.toLowerCase();
|
|
607
|
+
const reserveToken = isToken0 ? r0 : r1;
|
|
608
|
+
const reserveQuote = isToken0 ? r1 : r0;
|
|
609
|
+
return {
|
|
610
|
+
quoteToken: qt.address,
|
|
611
|
+
quoteSymbol: qt.symbol,
|
|
612
|
+
quoteDecimals: qt.decimals,
|
|
613
|
+
pairAddress,
|
|
614
|
+
reserveToken: formatBalance(reserveToken, shouldFormat, tokenDecimals),
|
|
615
|
+
reserveQuote: formatBalance(reserveQuote, shouldFormat, qt.decimals),
|
|
616
|
+
reserveQuoteRaw: reserveQuote,
|
|
617
|
+
reserveTokenRaw: reserveToken,
|
|
618
|
+
};
|
|
616
619
|
}
|
|
617
620
|
catch {
|
|
618
|
-
|
|
621
|
+
return null;
|
|
619
622
|
}
|
|
620
|
-
}
|
|
621
|
-
|
|
623
|
+
}));
|
|
624
|
+
// 过滤掉 null 结果
|
|
625
|
+
return pairResults.filter((r) => r !== null);
|
|
622
626
|
}
|
|
623
627
|
// ============================================================================
|
|
624
628
|
// V3 批量查询
|
|
@@ -715,43 +719,51 @@ async function queryV3Pools(token, factoryAddress, quoteTokens, feeTiers, provid
|
|
|
715
719
|
return results;
|
|
716
720
|
}
|
|
717
721
|
async function queryV3PoolsFallback(token, factoryAddress, quoteTokens, feeTiers, provider, tokenDecimals, shouldFormat) {
|
|
718
|
-
const results = [];
|
|
719
722
|
const factory = new Contract(factoryAddress, I_UNIV3_FACTORY_ABI, provider);
|
|
720
723
|
const tokenContract = new Contract(token, I_ERC20_ABI, provider);
|
|
724
|
+
// ✅ 构建所有查询组合
|
|
725
|
+
const queries = [];
|
|
721
726
|
for (const qt of quoteTokens) {
|
|
722
|
-
const
|
|
727
|
+
const tokenLower = token.toLowerCase();
|
|
728
|
+
const quoteLower = qt.address.toLowerCase();
|
|
729
|
+
const [token0, token1] = tokenLower < quoteLower
|
|
730
|
+
? [tokenLower, quoteLower]
|
|
731
|
+
: [quoteLower, tokenLower];
|
|
723
732
|
for (const fee of feeTiers) {
|
|
724
|
-
|
|
725
|
-
const tokenLower = token.toLowerCase();
|
|
726
|
-
const quoteLower = qt.address.toLowerCase();
|
|
727
|
-
const [token0, token1] = tokenLower < quoteLower
|
|
728
|
-
? [tokenLower, quoteLower]
|
|
729
|
-
: [quoteLower, tokenLower];
|
|
730
|
-
const poolAddress = await factory.getPool(token0, token1, fee);
|
|
731
|
-
if (poolAddress && poolAddress.toLowerCase() !== ZERO_ADDRESS) {
|
|
732
|
-
const [tokenBalance, quoteBalance] = await Promise.all([
|
|
733
|
-
tokenContract.balanceOf(poolAddress),
|
|
734
|
-
quoteContract.balanceOf(poolAddress),
|
|
735
|
-
]);
|
|
736
|
-
results.push({
|
|
737
|
-
quoteToken: qt.address,
|
|
738
|
-
quoteSymbol: qt.symbol,
|
|
739
|
-
quoteDecimals: qt.decimals,
|
|
740
|
-
pairAddress: poolAddress,
|
|
741
|
-
reserveToken: formatBalance(tokenBalance, shouldFormat, tokenDecimals),
|
|
742
|
-
reserveQuote: formatBalance(quoteBalance, shouldFormat, qt.decimals),
|
|
743
|
-
reserveQuoteRaw: quoteBalance,
|
|
744
|
-
reserveTokenRaw: tokenBalance, // ✅ 新增
|
|
745
|
-
fee,
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
catch {
|
|
750
|
-
// 跳过失败的查询
|
|
751
|
-
}
|
|
733
|
+
queries.push({ qt, fee, token0, token1 });
|
|
752
734
|
}
|
|
753
735
|
}
|
|
754
|
-
|
|
736
|
+
// ✅ 并行查询所有池子
|
|
737
|
+
const poolResults = await Promise.all(queries.map(async ({ qt, fee, token0, token1 }) => {
|
|
738
|
+
try {
|
|
739
|
+
const poolAddress = await factory.getPool(token0, token1, fee);
|
|
740
|
+
if (!poolAddress || poolAddress.toLowerCase() === ZERO_ADDRESS) {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
const quoteContract = new Contract(qt.address, I_ERC20_ABI, provider);
|
|
744
|
+
// ✅ 并行获取余额
|
|
745
|
+
const [tokenBalance, quoteBalance] = await Promise.all([
|
|
746
|
+
tokenContract.balanceOf(poolAddress),
|
|
747
|
+
quoteContract.balanceOf(poolAddress),
|
|
748
|
+
]);
|
|
749
|
+
return {
|
|
750
|
+
quoteToken: qt.address,
|
|
751
|
+
quoteSymbol: qt.symbol,
|
|
752
|
+
quoteDecimals: qt.decimals,
|
|
753
|
+
pairAddress: poolAddress,
|
|
754
|
+
reserveToken: formatBalance(tokenBalance, shouldFormat, tokenDecimals),
|
|
755
|
+
reserveQuote: formatBalance(quoteBalance, shouldFormat, qt.decimals),
|
|
756
|
+
reserveQuoteRaw: quoteBalance,
|
|
757
|
+
reserveTokenRaw: tokenBalance,
|
|
758
|
+
fee,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
}));
|
|
765
|
+
// 过滤掉 null 结果
|
|
766
|
+
return poolResults.filter((r) => r !== null);
|
|
755
767
|
}
|
|
756
768
|
// ============================================================================
|
|
757
769
|
// 平台类型判断
|
|
@@ -52,12 +52,9 @@ async function _stealthTransferInternal(params) {
|
|
|
52
52
|
if (rootBal < needed) {
|
|
53
53
|
throw new Error(`insufficient balance: need ${needed.toString()}, have ${rootBal.toString()}`);
|
|
54
54
|
}
|
|
55
|
-
//
|
|
56
|
-
const
|
|
57
|
-
nonces.
|
|
58
|
-
for (const w of hops) {
|
|
59
|
-
nonces.push(await provider.getTransactionCount(w.address, 'pending'));
|
|
60
|
-
}
|
|
55
|
+
// ✅ 并行预取所有 nonce
|
|
56
|
+
const allAddresses = [rootAddr, ...hops.map(w => w.address)];
|
|
57
|
+
const nonces = await Promise.all(allAddresses.map(addr => provider.getTransactionCount(addr, 'pending')));
|
|
61
58
|
const signed = [];
|
|
62
59
|
const hopAddresses = [];
|
|
63
60
|
if (mode !== 'erc20') {
|
|
@@ -73,14 +70,14 @@ async function _stealthTransferInternal(params) {
|
|
|
73
70
|
type: 0,
|
|
74
71
|
});
|
|
75
72
|
signed.push(fundTx);
|
|
76
|
-
// 2)
|
|
77
|
-
|
|
73
|
+
// 2) ✅ 并行签名所有跳转交易
|
|
74
|
+
const hopTxs = await Promise.all(hops.map(async (hop, i) => {
|
|
78
75
|
const isLast = i === hops.length - 1;
|
|
79
76
|
const nextTo = isLast ? finalTo : hops[i + 1].address;
|
|
80
77
|
const remain = BigInt(hops.length - 1 - i);
|
|
81
78
|
const value = isLast ? amountWei : amountWei + perHopFee * remain;
|
|
82
|
-
hopAddresses.push(
|
|
83
|
-
|
|
79
|
+
hopAddresses.push(hop.address);
|
|
80
|
+
return hop.signTransaction({
|
|
84
81
|
to: nextTo,
|
|
85
82
|
value,
|
|
86
83
|
nonce: nonces[i + 1],
|
|
@@ -89,61 +86,61 @@ async function _stealthTransferInternal(params) {
|
|
|
89
86
|
chainId,
|
|
90
87
|
type: 0,
|
|
91
88
|
});
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
}));
|
|
90
|
+
signed.push(...hopTxs);
|
|
94
91
|
}
|
|
95
92
|
else {
|
|
96
93
|
// ============== ERC20 代币多跳路径 ==============
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
94
|
+
const erc20Iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
95
|
+
const transferGas = erc20?.transferGasLimit ?? 65000n;
|
|
96
|
+
// 填充 hopAddresses
|
|
97
|
+
hops.forEach(hop => hopAddresses.push(hop.address));
|
|
98
|
+
// ✅ 并行签名所有 gas 注资交易(root -> hop[i])
|
|
99
|
+
const fundTxs = await Promise.all(hops.map((hop, i) => root.signTransaction({
|
|
100
|
+
to: hop.address,
|
|
101
|
+
value: transferGas * gasPrice,
|
|
102
|
+
nonce: nonces[0] + i,
|
|
103
|
+
gasPrice,
|
|
104
|
+
gasLimit,
|
|
105
|
+
chainId,
|
|
106
|
+
type: 0,
|
|
107
|
+
})));
|
|
108
|
+
signed.push(...fundTxs);
|
|
112
109
|
// 代币持有者:可选独立私钥,未提供则用 root
|
|
113
110
|
const tokenOwner = (erc20?.holderPrivateKey && erc20.holderPrivateKey.length > 0)
|
|
114
111
|
? new ethers.Wallet(erc20.holderPrivateKey, provider)
|
|
115
112
|
: root;
|
|
116
|
-
// tokenOwner -> hop0: ERC20 transfer 全量 token
|
|
117
|
-
const erc20Iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
113
|
+
// tokenOwner -> hop0: ERC20 transfer 全量 token
|
|
118
114
|
const callDataRoot = erc20Iface.encodeFunctionData('transfer', [hops[0].address, amountWei]);
|
|
115
|
+
const rootTokenNonce = nonces[0] + hops.length;
|
|
119
116
|
const erc20TxRoot = await tokenOwner.signTransaction({
|
|
120
117
|
to: erc20.address,
|
|
121
118
|
data: callDataRoot,
|
|
122
119
|
value: 0n,
|
|
123
|
-
nonce:
|
|
120
|
+
nonce: rootTokenNonce,
|
|
124
121
|
gasPrice,
|
|
125
|
-
gasLimit:
|
|
122
|
+
gasLimit: transferGas,
|
|
126
123
|
chainId,
|
|
127
124
|
type: 0,
|
|
128
125
|
});
|
|
129
126
|
signed.push(erc20TxRoot);
|
|
130
|
-
// hop
|
|
131
|
-
|
|
127
|
+
// ✅ 并行签名所有 hop 的 ERC20 transfer
|
|
128
|
+
const hopTokenTxs = await Promise.all(hops.map((hop, i) => {
|
|
132
129
|
const isLast = i === hops.length - 1;
|
|
133
130
|
const nextTo = isLast ? finalTo : hops[i + 1].address;
|
|
134
131
|
const callData = erc20Iface.encodeFunctionData('transfer', [nextTo, amountWei]);
|
|
135
|
-
|
|
132
|
+
return hop.signTransaction({
|
|
136
133
|
to: erc20.address,
|
|
137
134
|
data: callData,
|
|
138
135
|
value: 0n,
|
|
139
136
|
nonce: nonces[i + 1],
|
|
140
137
|
gasPrice,
|
|
141
|
-
gasLimit:
|
|
138
|
+
gasLimit: transferGas,
|
|
142
139
|
chainId,
|
|
143
140
|
type: 0,
|
|
144
141
|
});
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
}));
|
|
143
|
+
signed.push(...hopTokenTxs);
|
|
147
144
|
}
|
|
148
145
|
// 3) Bundle 提交
|
|
149
146
|
const club48 = new Club48Client({ endpoint: bundleEndpoint });
|
|
@@ -112,20 +112,24 @@ export async function calculateSellAmount(provider, tokenAddress, walletAddress,
|
|
|
112
112
|
if (sellAmount && sellPercentage) {
|
|
113
113
|
throw new Error('sellAmount 和 sellPercentage 不能同时指定');
|
|
114
114
|
}
|
|
115
|
-
|
|
116
|
-
// 精确数量
|
|
115
|
+
// 精确数量:只需要 decimals
|
|
117
116
|
if (sellAmount) {
|
|
117
|
+
const decimals = await getTokenDecimals(provider, tokenAddress);
|
|
118
118
|
return {
|
|
119
119
|
amount: ethers.parseUnits(sellAmount, decimals),
|
|
120
120
|
decimals
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
|
-
//
|
|
123
|
+
// 百分比:需要 decimals 和 balance
|
|
124
124
|
if (sellPercentage !== undefined) {
|
|
125
125
|
if (sellPercentage <= 0 || sellPercentage > 100) {
|
|
126
126
|
throw new Error('sellPercentage 必须在 0-100 之间');
|
|
127
127
|
}
|
|
128
|
-
|
|
128
|
+
// ✅ 并行获取 decimals 和 balance
|
|
129
|
+
const [decimals, balance] = await Promise.all([
|
|
130
|
+
getTokenDecimals(provider, tokenAddress),
|
|
131
|
+
getTokenBalance(provider, tokenAddress, walletAddress)
|
|
132
|
+
]);
|
|
129
133
|
const amount = (balance * BigInt(Math.floor(sellPercentage * 100))) / 10000n;
|
|
130
134
|
return { amount, decimals };
|
|
131
135
|
}
|