four-flap-meme-sdk 4.3.21 → 4.4.0
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/bundle/four-meme/approve-tokenmanager.js +46 -16
- package/dist/bundle/four-meme/core/create-token-quote.d.ts +5 -1
- package/dist/bundle/four-meme/core/create-token-quote.js +29 -4
- package/dist/bundle/four-meme/core/sell-flow.js +3 -3
- package/dist/bundle/four-meme/pancake-proxy/approve.js +47 -10
- package/dist/bundle/four-meme/private/sell.js +2 -10
- package/dist/bundle/four-meme/swap/bundle.js +4 -4
- package/dist/bundle/four-meme/swap-buy-first/multi-wallet.js +25 -8
- package/dist/bundle/four-meme/swap-buy-first/single.js +30 -9
- package/dist/bundle/four-meme/swap-sign-helpers.js +56 -3
- package/dist/bundle/four-meme/utils-sweep/erc20-no-hop.js +18 -7
- package/dist/bundle/four-meme/utils-sweep/with-hops.js +10 -4
- package/dist/bundle/internal.d.ts +5 -0
- package/dist/bundle/internal.js +36 -0
- package/dist/chains/bsc/pancake/bundle-buy-first-quote.js +24 -8
- package/dist/chains/bsc/pancake/bundle-buy-first-types.d.ts +2 -0
- package/dist/chains/bsc/pancake/bundle-buy-first-validate.d.ts +1 -1
- package/dist/chains/bsc/pancake/bundle-buy-first-validate.js +2 -2
- package/dist/chains/bsc/pancake/bundle-buy-first.js +1 -0
- package/dist/chains/bsc/pancake/bundle-swap-types.d.ts +2 -0
- package/dist/chains/bsc/pancake/quote.d.ts +7 -0
- package/dist/chains/bsc/pancake/quote.js +9 -1
- package/dist/chains/bsc/pancake/swap-batch.js +15 -16
- package/dist/chains/bsc/pancake/swap-build.d.ts +1 -1
- package/dist/chains/bsc/pancake/swap-build.js +15 -9
- package/dist/chains/bsc/pancake/swap-quick.js +4 -4
- package/dist/chains/bsc/pancake/swap-single.js +1 -0
- package/dist/chains/bsc/platforms/iro/token.d.ts +1 -0
- package/dist/chains/bsc/platforms/iro/token.js +36 -1
- package/dist/chains/eni/batch-router/transfer-sweep.js +10 -14
- package/dist/chains/eni/platforms/daoaas/graduation.js +60 -14
- package/dist/chains/eni/platforms/daoaas/meta.d.ts +0 -4
- package/dist/chains/eni/platforms/daoaas/meta.js +112 -27
- package/dist/chains/eni/platforms/daoaas/portal-direct-submit.js +11 -6
- package/dist/chains/eni/platforms/daoaas/portal-direct-tx.js +4 -2
- package/dist/chains/eni/platforms/daoaas/portal.d.ts +5 -0
- package/dist/chains/eni/platforms/daoaas/portal.js +63 -9
- package/dist/chains/eni/platforms/dswap/liquidity.js +144 -49
- package/dist/chains/eni/platforms/fair-launch/launcher.js +2 -10
- package/dist/chains/eni/platforms/fair-launch/pool.d.ts +1 -0
- package/dist/chains/eni/platforms/fair-launch/pool.js +16 -2
- package/dist/chains/eni/platforms/iro/token.d.ts +1 -0
- package/dist/chains/eni/platforms/iro/token.js +36 -1
- package/dist/chains/robinhood/constants.d.ts +2 -5
- package/dist/chains/robinhood/constants.js +8 -7
- package/dist/chains/robinhood/longetf/launch.js +4 -4
- package/dist/chains/robinhood/pons-v2/launch.js +51 -20
- package/dist/chains/xlayer/eip7702/bundle-approve.js +8 -8
- package/dist/chains/xlayer/eip7702/bundle-sell.js +3 -4
- package/dist/chains/xlayer/eip7702/bundle-swap-batch.js +4 -2
- package/dist/chains/xlayer/eip7702/bundle-swap-multi.js +3 -3
- package/dist/chains/xlayer/eip7702/multi-hop-transfer-sweep.js +4 -10
- package/dist/chains/xlayer/eip7702/volume-bundle.js +45 -24
- package/dist/chains/xlayer/eoa/eoa-wash-volume.js +4 -14
- package/dist/core/constants/gas.d.ts +0 -8
- package/dist/core/constants/gas.js +0 -9
- package/dist/core/quote/quote-helpers.d.ts +11 -0
- package/dist/core/quote/quote-helpers.js +121 -20
- package/dist/core/quote/quote-helpers.test.js +11 -1
- package/dist/domains/dex/direct-router-v2-buy.js +4 -2
- package/dist/domains/dex/direct-router-v2-sell.js +63 -12
- package/dist/domains/dex/direct-router-v3.js +3 -4
- package/dist/domains/dex/quote.js +15 -5
- package/dist/domains/flap/portal-bundle-merkle/pancake-proxy/approve.js +48 -8
- package/dist/domains/flap/portal-bundle-merkle/swap-batch.js +8 -10
- package/dist/domains/flap/portal-bundle-merkle/swap-buy-first-quote.js +24 -9
- package/dist/domains/flap/portal-bundle-merkle/swap-planning.js +6 -2
- package/dist/domains/flap/portal-bundle-merkle/utils/sweep.js +14 -7
- package/dist/domains/flap/reader.d.ts +6 -0
- package/dist/domains/flap/reader.js +77 -1
- package/dist/domains/four/contracts/tm-bundle-batch.js +2 -10
- package/dist/domains/four/contracts/tm-bundle-create.js +5 -2
- package/dist/domains/four/contracts/tm.d.ts +2 -0
- package/dist/domains/four/contracts/tm.js +59 -2
- package/dist/domains/four/tax-token.js +60 -28
- package/dist/domains/lp-inspect/inspect-tax.js +98 -12
- package/dist/domains/lp-inspect/inspect.js +57 -44
- package/dist/domains/lp-inspect/registry.js +33 -7
- package/dist/domains/openfour/registry.js +15 -5
- package/dist/utils/airdrop-sweep.js +37 -17
- package/dist/utils/swap-helpers.js +35 -7
- package/package.json +1 -1
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* inspectTokenLP 主查询流程。V2/V3 批量查询、fallback、平台判断与格式化工具
|
|
5
5
|
* 已抽取到 inspect-helpers.ts(内部 helper,不对外暴露)。
|
|
6
6
|
*/
|
|
7
|
-
import { Contract, formatEther } from 'ethers';
|
|
8
|
-
import { ADDRESSES, ZERO_ADDRESS } from '../../core/constants/index.js';
|
|
7
|
+
import { Contract, Interface, formatEther } from 'ethers';
|
|
8
|
+
import { ADDRESSES, CHAINS, ZERO_ADDRESS, getMulticall3Address } from '../../core/constants/index.js';
|
|
9
9
|
import { Helper3 } from '../four/contracts/helper3.js';
|
|
10
10
|
import { FlapPortal } from '../flap/portal.js';
|
|
11
11
|
import { DEFAULT_V3_FEE_TIERS } from './lp-inspect-helpers.js';
|
|
@@ -13,6 +13,16 @@ import { getChainConfig } from './registry.js';
|
|
|
13
13
|
import { determinePlatform, formatBalance, getProvider, getTokenDecimals, normalizeLiquidity, queryV2Pairs, queryV3Pools, } from './inspect-helpers.js';
|
|
14
14
|
import { flapV8TaxBps, isFlapInnerMarket, readBscOuterTaxBps } from './inspect-tax.js';
|
|
15
15
|
const MULTICALL3_ADDRESS = ADDRESSES.BSC.Multicall3;
|
|
16
|
+
const AGGREGATE3_ABI = [
|
|
17
|
+
'function aggregate3((address target,bool allowFailure,bytes callData)[] calls) view returns ((bool success,bytes returnData)[])',
|
|
18
|
+
];
|
|
19
|
+
async function readViews(provider, multicall3, calls) {
|
|
20
|
+
const multicall = new Contract(multicall3, AGGREGATE3_ABI, provider);
|
|
21
|
+
const aggregate3 = multicall.aggregate3;
|
|
22
|
+
if (!aggregate3)
|
|
23
|
+
throw new Error('aggregate3 missing');
|
|
24
|
+
return (await aggregate3.staticCall(calls.map((call) => ({ target: call.target, allowFailure: true, callData: call.callData }))));
|
|
25
|
+
}
|
|
16
26
|
// ============================================================================
|
|
17
27
|
// 主查询函数
|
|
18
28
|
// ============================================================================
|
|
@@ -40,13 +50,20 @@ export async function inspectTokenLP(token, opts) {
|
|
|
40
50
|
let tokenDecimals;
|
|
41
51
|
let totalSupplyRaw;
|
|
42
52
|
try {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
const tokenIface = new Interface([
|
|
54
|
+
'function decimals() view returns (uint8)',
|
|
55
|
+
'function totalSupply() view returns (uint256)',
|
|
56
|
+
]);
|
|
57
|
+
const rows = await readViews(provider, multicall3, [
|
|
58
|
+
{ target: token, callData: tokenIface.encodeFunctionData('decimals', []) },
|
|
59
|
+
{ target: token, callData: tokenIface.encodeFunctionData('totalSupply', []) },
|
|
47
60
|
]);
|
|
61
|
+
const decimals = rows[0]?.success ? tokenIface.decodeFunctionResult('decimals', rows[0].returnData)[0] : 18;
|
|
62
|
+
const supply = rows[1]?.success
|
|
63
|
+
? tokenIface.decodeFunctionResult('totalSupply', rows[1].returnData)[0]
|
|
64
|
+
: undefined;
|
|
48
65
|
tokenDecimals = Number(decimals);
|
|
49
|
-
totalSupplyRaw = supply
|
|
66
|
+
totalSupplyRaw = supply;
|
|
50
67
|
result.decimals = tokenDecimals;
|
|
51
68
|
if (totalSupplyRaw !== undefined) {
|
|
52
69
|
result.totalSupplyRaw = totalSupplyRaw;
|
|
@@ -65,25 +82,13 @@ export async function inspectTokenLP(token, opts) {
|
|
|
65
82
|
let st = null;
|
|
66
83
|
let taxRate;
|
|
67
84
|
const useBscV8 = opts.flapChain === 'BSC';
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
taxRate = flapV8TaxBps(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// V8 不可用时只借 V7/V5 读盘面,不用 V7 残留 taxRate(常见 100 bps)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (!st) {
|
|
79
|
-
try {
|
|
80
|
-
const stV7 = await flap.getTokenV7(token);
|
|
81
|
-
st = stV7;
|
|
82
|
-
if (!useBscV8)
|
|
83
|
-
taxRate = Number(stV7.taxRate ?? 0n);
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
st = await flap.getTokenV5(token);
|
|
85
|
+
const preferred = await flap.readPreferredTokenState(token, useBscV8);
|
|
86
|
+
if (preferred) {
|
|
87
|
+
st = preferred;
|
|
88
|
+
if (useBscV8 && 'buyTaxRate' in preferred)
|
|
89
|
+
taxRate = flapV8TaxBps(preferred);
|
|
90
|
+
else if (!useBscV8 && 'taxRate' in preferred && !('buyTaxRate' in preferred)) {
|
|
91
|
+
taxRate = Number(preferred.taxRate ?? 0n);
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
if (st && isFlapInnerMarket(st.status)) {
|
|
@@ -125,22 +130,29 @@ export async function inspectTokenLP(token, opts) {
|
|
|
125
130
|
if (opts.chain === 'ENI') {
|
|
126
131
|
try {
|
|
127
132
|
const portalAddress = ADDRESSES.ENI.DaoaasPortal;
|
|
128
|
-
const
|
|
133
|
+
const portalIface = new Interface([
|
|
129
134
|
'function getTokenState(address token) view returns (uint256)',
|
|
130
135
|
'function getPrice(address token) view returns (uint256)',
|
|
131
136
|
'function getOKAmountBySale(address token, uint256 tokenAmount) view returns (uint256)',
|
|
132
|
-
];
|
|
133
|
-
const
|
|
134
|
-
|
|
137
|
+
]);
|
|
138
|
+
const tokenIface = new Interface([
|
|
139
|
+
'function totalSupply() view returns (uint256)',
|
|
140
|
+
'function balanceOf(address) view returns (uint256)',
|
|
141
|
+
]);
|
|
142
|
+
const portal = new Contract(portalAddress, portalIface, provider);
|
|
143
|
+
const eniRows = await readViews(provider, getMulticall3Address(CHAINS.ENI.chainId), [
|
|
144
|
+
{ target: portalAddress, callData: portalIface.encodeFunctionData('getTokenState', [token]) },
|
|
145
|
+
{ target: portalAddress, callData: portalIface.encodeFunctionData('getPrice', [token]) },
|
|
146
|
+
{ target: token, callData: tokenIface.encodeFunctionData('totalSupply', []) },
|
|
147
|
+
{ target: token, callData: tokenIface.encodeFunctionData('balanceOf', [portalAddress]) },
|
|
148
|
+
]);
|
|
149
|
+
const status = Number(portalIface.decodeFunctionResult('getTokenState', eniRows[0].returnData)[0]);
|
|
135
150
|
// status: 0=不存在, 1=交易中(内盘), 2=已毕业(外盘)
|
|
136
151
|
if (status === 1) {
|
|
137
152
|
result.platform = 'DAOAAS';
|
|
138
|
-
const price = (
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
tokenContract.totalSupply(),
|
|
142
|
-
tokenContract.balanceOf(portalAddress),
|
|
143
|
-
]);
|
|
153
|
+
const price = portalIface.decodeFunctionResult('getPrice', eniRows[1].returnData)[0];
|
|
154
|
+
const totalSupply = tokenIface.decodeFunctionResult('totalSupply', eniRows[2].returnData)[0];
|
|
155
|
+
const portalTokenBalance = tokenIface.decodeFunctionResult('balanceOf', eniRows[3].returnData)[0];
|
|
144
156
|
const circulatingSupply = totalSupply - portalTokenBalance;
|
|
145
157
|
let reserveNative = '0';
|
|
146
158
|
try {
|
|
@@ -339,16 +351,17 @@ export async function inspectTokenLP(token, opts) {
|
|
|
339
351
|
try {
|
|
340
352
|
const flap = new FlapPortal({ chain: opts.flapChain, rpcUrl: opts.rpcUrl });
|
|
341
353
|
let taxRate = 0;
|
|
354
|
+
const preferred = await flap.readPreferredTokenState(token, opts.flapChain === 'BSC');
|
|
342
355
|
if (opts.flapChain === 'BSC') {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
356
|
+
if (preferred && 'buyTaxRate' in preferred) {
|
|
357
|
+
taxRate = flapV8TaxBps(preferred);
|
|
358
|
+
// V8 已把「无池残留 100」收成 0;毕业盘真实 1%(100 bps)要留下
|
|
359
|
+
if (taxRate > 0)
|
|
360
|
+
result.taxRate = taxRate;
|
|
361
|
+
}
|
|
348
362
|
}
|
|
349
|
-
else {
|
|
350
|
-
|
|
351
|
-
taxRate = Number(stV7.taxRate ?? 0n);
|
|
363
|
+
else if (preferred && 'taxRate' in preferred && !('buyTaxRate' in preferred)) {
|
|
364
|
+
taxRate = Number(preferred.taxRate ?? 0n);
|
|
352
365
|
if (taxRate > 0 && taxRate !== 100)
|
|
353
366
|
result.taxRate = taxRate;
|
|
354
367
|
}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* DEX/稳定币注册、链配置获取、报价代币列表等注册表相关 API。
|
|
5
5
|
*/
|
|
6
|
-
import { Contract } from 'ethers';
|
|
6
|
+
import { Contract, Interface } from 'ethers';
|
|
7
|
+
import { MULTICALL3_ABI } from '../../abis/common.js';
|
|
8
|
+
import { getMulticall3Address } from '../../core/constants/addresses.js';
|
|
7
9
|
import { createProvider } from '../../utils/provider-factory.js';
|
|
8
10
|
import { ADDRESSES } from '../../core/constants/index.js';
|
|
9
11
|
import { getErrorMessageFromUnknown } from '../../core/index.js';
|
|
@@ -22,12 +24,36 @@ export async function getFactoryFromRouter(routerAddress, rpcUrl, routerType = '
|
|
|
22
24
|
}
|
|
23
25
|
else {
|
|
24
26
|
// V3 SwapRouter02 可能同时有 V2 和 V3 Factory
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const routerIface = new Interface(V3_ROUTER_ABI);
|
|
28
|
+
const multicall = new Contract(getMulticall3Address(56), MULTICALL3_ABI, provider);
|
|
29
|
+
const aggregate3 = multicall.aggregate3;
|
|
30
|
+
let v3Factory;
|
|
31
|
+
let v2Factory;
|
|
32
|
+
if (aggregate3) {
|
|
33
|
+
const rows = (await aggregate3.staticCall([
|
|
34
|
+
{
|
|
35
|
+
target: routerAddress,
|
|
36
|
+
allowFailure: true,
|
|
37
|
+
callData: routerIface.encodeFunctionData('factory', []),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
target: routerAddress,
|
|
41
|
+
allowFailure: true,
|
|
42
|
+
callData: routerIface.encodeFunctionData('factoryV2', []),
|
|
43
|
+
},
|
|
44
|
+
]));
|
|
45
|
+
if (rows[0]?.success && rows[0].returnData !== '0x') {
|
|
46
|
+
v3Factory = routerIface.decodeFunctionResult('factory', rows[0].returnData)[0];
|
|
47
|
+
}
|
|
48
|
+
if (rows[1]?.success && rows[1].returnData !== '0x') {
|
|
49
|
+
v2Factory = routerIface.decodeFunctionResult('factoryV2', rows[1].returnData)[0];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const router = new Contract(routerAddress, V3_ROUTER_ABI, provider);
|
|
54
|
+
v3Factory = await router.factory().catch(() => undefined);
|
|
55
|
+
v2Factory = await router.factoryV2().catch(() => undefined);
|
|
56
|
+
}
|
|
31
57
|
if (v3Factory)
|
|
32
58
|
result.v3Factory = v3Factory;
|
|
33
59
|
if (v2Factory)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Contract } from 'ethers';
|
|
2
|
-
import {
|
|
1
|
+
import { Contract, Interface } from 'ethers';
|
|
2
|
+
import { MULTICALL3_ABI } from '../../abis/common.js';
|
|
3
|
+
import { ADDRESSES, CHAINS, getMulticall3Address } from '../../core/constants/index.js';
|
|
3
4
|
import { OPENFOUR_CORE_ABI, OPENFOUR_REGISTRY_ABI, OPENFOUR_TOOLS_ABI } from '../../abis/openfour.js';
|
|
4
5
|
function mapParam(raw) {
|
|
5
6
|
return {
|
|
@@ -23,11 +24,20 @@ function mapSchema(raw) {
|
|
|
23
24
|
}
|
|
24
25
|
export async function loadOpenFourAddresses(provider, registryAddress) {
|
|
25
26
|
const registry = registryAddress || ADDRESSES.BSC.OpenFourRegistry;
|
|
26
|
-
const
|
|
27
|
-
const
|
|
27
|
+
const registryIface = new Interface(OPENFOUR_REGISTRY_ABI);
|
|
28
|
+
const multicall = new Contract(getMulticall3Address(CHAINS.BSC.chainId), MULTICALL3_ABI, provider);
|
|
29
|
+
const aggregate3 = multicall.aggregate3;
|
|
30
|
+
if (!aggregate3)
|
|
31
|
+
throw new Error('aggregate3 missing');
|
|
32
|
+
const rows = (await aggregate3.staticCall([
|
|
33
|
+
{ target: registry, allowFailure: false, callData: registryIface.encodeFunctionData('openFourCore', []) },
|
|
34
|
+
{ target: registry, allowFailure: false, callData: registryIface.encodeFunctionData('openFourTool', []) },
|
|
35
|
+
]));
|
|
36
|
+
const core = registryIface.decodeFunctionResult('openFourCore', rows[0].returnData)[0];
|
|
37
|
+
const tools = registryIface.decodeFunctionResult('openFourTool', rows[1].returnData)[0];
|
|
28
38
|
const coreC = new Contract(core, OPENFOUR_CORE_ABI, provider);
|
|
29
39
|
const wrappedNative = (await coreC.wrappedNative());
|
|
30
|
-
return { registry, core
|
|
40
|
+
return { registry, core, tools, wrappedNative };
|
|
31
41
|
}
|
|
32
42
|
export async function loadOpenFourPresetSchemas(provider, presetId, toolsAddress) {
|
|
33
43
|
const toolsAddr = toolsAddress ?? (await loadOpenFourAddresses(provider)).tools;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { ethers, JsonRpcProvider } from 'ethers';
|
|
2
|
-
import {
|
|
1
|
+
import { ethers, Interface, JsonRpcProvider } from 'ethers';
|
|
2
|
+
import { MULTICALL3_ABI } from '../abis/common.js';
|
|
3
|
+
import { GAS_LIMITS, getMulticall3Address, isEniChain } from '../core/constants/index.js';
|
|
3
4
|
import { buildGasFields } from '../core/gas/bundle-gas.js';
|
|
4
5
|
import { getErc20Decimals } from '../core/erc20/decimals.js';
|
|
6
|
+
import { batchGetBalances } from '../bundle/internal.js';
|
|
5
7
|
function getTxTypeForChain(chainId) {
|
|
6
8
|
return isEniChain(chainId) ? 2 : 0;
|
|
7
9
|
}
|
|
@@ -103,7 +105,7 @@ export async function sweepWithBundle(params) {
|
|
|
103
105
|
const gasCost = nativeGasLimit * gasPrice;
|
|
104
106
|
// 并行获取所有余额和 nonce
|
|
105
107
|
const [balances, nonces] = await Promise.all([
|
|
106
|
-
|
|
108
|
+
batchGetBalances(provider, wallets.map((w) => w.address), undefined, chainId),
|
|
107
109
|
Promise.all(wallets.map((w) => provider.getTransactionCount(w.address, 'pending'))),
|
|
108
110
|
]);
|
|
109
111
|
// 计算每个钱包的发送金额
|
|
@@ -140,22 +142,40 @@ export async function sweepWithBundle(params) {
|
|
|
140
142
|
'function transfer(address,uint256) returns (bool)',
|
|
141
143
|
]);
|
|
142
144
|
// ✅ 并行获取 decimals、所有代币余额和 nonce
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
const owners = wallets.map((w) => w.address);
|
|
146
|
+
const sweepIface = new Interface([
|
|
147
|
+
'function decimals() view returns (uint8)',
|
|
148
|
+
'function balanceOf(address) view returns (uint256)',
|
|
149
|
+
]);
|
|
150
|
+
const [viewRows, nonces] = await Promise.all([
|
|
151
|
+
(async () => {
|
|
152
|
+
const multicall = new ethers.Contract(getMulticall3Address(chainId), MULTICALL3_ABI, provider);
|
|
153
|
+
const aggregate3 = multicall.aggregate3;
|
|
154
|
+
if (!aggregate3)
|
|
155
|
+
throw new Error('aggregate3 missing');
|
|
156
|
+
return (await aggregate3.staticCall([
|
|
157
|
+
{ target: tokenAddress, allowFailure: false, callData: sweepIface.encodeFunctionData('decimals', []) },
|
|
158
|
+
...owners.map((owner) => ({
|
|
159
|
+
target: tokenAddress,
|
|
160
|
+
allowFailure: true,
|
|
161
|
+
callData: sweepIface.encodeFunctionData('balanceOf', [owner]),
|
|
162
|
+
})),
|
|
163
|
+
]));
|
|
164
|
+
})(),
|
|
157
165
|
Promise.all(wallets.map((w) => provider.getTransactionCount(w.address, 'pending'))),
|
|
158
166
|
]);
|
|
167
|
+
const decimals = Number(sweepIface.decodeFunctionResult('decimals', viewRows[0].returnData)[0]);
|
|
168
|
+
const balanceResults = owners.map((_, index) => {
|
|
169
|
+
const row = viewRows[index + 1];
|
|
170
|
+
if (!row?.success || row.returnData === '0x')
|
|
171
|
+
return 0n;
|
|
172
|
+
try {
|
|
173
|
+
return sweepIface.decodeFunctionResult('balanceOf', row.returnData)[0];
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return 0n;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
159
179
|
// 计算每个钱包的发送金额
|
|
160
180
|
const sendAmounts = balanceResults.map((bal) => {
|
|
161
181
|
if (ratio !== undefined) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 换手交易通用工具函数
|
|
3
3
|
*/
|
|
4
|
-
import { ethers, Contract } from 'ethers';
|
|
4
|
+
import { ethers, Contract, Interface } from 'ethers';
|
|
5
5
|
import { batchCheckAllowances } from '../core/erc20/index.js';
|
|
6
|
-
import { ERC20_ABI } from '../abis/common.js';
|
|
6
|
+
import { ERC20_ABI, MULTICALL3_ABI } from '../abis/common.js';
|
|
7
|
+
import { getMulticall3Address } from '../core/constants/index.js';
|
|
7
8
|
import { NonceManager, getOptimizedGasPrice } from './bundle-helpers.js';
|
|
8
9
|
const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
|
|
9
10
|
/**
|
|
@@ -83,6 +84,37 @@ export async function ensureTokenApproval(provider, merkle, wallet, tokenAddress
|
|
|
83
84
|
throw new Error(`授权失败: ${results[0]?.error || '未知错误'}`);
|
|
84
85
|
}
|
|
85
86
|
}
|
|
87
|
+
async function readDecimalsAndBalance(provider, tokenAddress, walletAddress) {
|
|
88
|
+
try {
|
|
89
|
+
const chainId = Number((await provider.getNetwork()).chainId);
|
|
90
|
+
const iface = new Interface([
|
|
91
|
+
'function decimals() view returns (uint8)',
|
|
92
|
+
'function balanceOf(address) view returns (uint256)',
|
|
93
|
+
]);
|
|
94
|
+
const multicall = new Contract(getMulticall3Address(chainId), MULTICALL3_ABI, provider);
|
|
95
|
+
const aggregate3 = multicall.aggregate3;
|
|
96
|
+
if (!aggregate3)
|
|
97
|
+
throw new Error('aggregate3 missing');
|
|
98
|
+
const rows = (await aggregate3.staticCall([
|
|
99
|
+
{ target: tokenAddress, allowFailure: false, callData: iface.encodeFunctionData('decimals', []) },
|
|
100
|
+
{
|
|
101
|
+
target: tokenAddress,
|
|
102
|
+
allowFailure: false,
|
|
103
|
+
callData: iface.encodeFunctionData('balanceOf', [walletAddress]),
|
|
104
|
+
},
|
|
105
|
+
]));
|
|
106
|
+
return [
|
|
107
|
+
Number(iface.decodeFunctionResult('decimals', rows[0].returnData)[0]),
|
|
108
|
+
iface.decodeFunctionResult('balanceOf', rows[1].returnData)[0],
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return Promise.all([
|
|
113
|
+
getTokenDecimals(provider, tokenAddress),
|
|
114
|
+
getTokenBalance(provider, tokenAddress, walletAddress),
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
86
118
|
/**
|
|
87
119
|
* 获取代币余额
|
|
88
120
|
*/
|
|
@@ -140,11 +172,7 @@ export async function calculateSellAmount(provider, tokenAddress, walletAddress,
|
|
|
140
172
|
if (sellPercentage <= 0 || sellPercentage > 100) {
|
|
141
173
|
throw new Error('sellPercentage 必须在 0-100 之间');
|
|
142
174
|
}
|
|
143
|
-
|
|
144
|
-
const [decimals, balance] = await Promise.all([
|
|
145
|
-
getTokenDecimals(provider, tokenAddress),
|
|
146
|
-
getTokenBalance(provider, tokenAddress, walletAddress),
|
|
147
|
-
]);
|
|
175
|
+
const [decimals, balance] = await readDecimalsAndBalance(provider, tokenAddress, walletAddress);
|
|
148
176
|
const amount = (balance * BigInt(Math.floor(sellPercentage * 100))) / 10000n;
|
|
149
177
|
return { amount, decimals };
|
|
150
178
|
}
|