four-flap-meme-sdk 2.2.0 → 2.2.1
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-core/config-helpers.d.ts +5 -5
- package/dist/bundle-core/errors.js +2 -1
- package/dist/chains/bsc/pancake/bundle-swap-helpers.d.ts +1 -1
- package/dist/chains/bsc/platforms/iro/factory.d.ts +2 -2
- package/dist/chains/eni/bundler/sign.js +1 -2
- package/dist/chains/eni/platforms/daoaas/meta.js +2 -3
- package/dist/chains/eni/platforms/iro/factory.d.ts +2 -2
- package/dist/chains/xlayer/eip7702/bundle-sell.js +3 -14
- package/dist/chains/xlayer/eip7702/multi-hop-transfer-helpers.d.ts +1 -1
- package/dist/chains/xlayer/eip7702/multi-hop-transfer.js +13 -14
- package/dist/chains/xlayer/eip7702/volume.js +10 -10
- package/dist/merkle/index.d.ts +1 -1
- package/dist/merkle/index.js +1 -1
- package/dist/shared/clients/four.d.ts +21 -6
- package/dist/shared/clients/four.js +8 -7
- package/dist/shared/flap/meta.d.ts +7 -1
- package/dist/shared/flap/pinata.d.ts +12 -3
- package/dist/shared/flap/pinata.js +9 -19
- package/dist/shared/flap/portal-bundle-merkle/create-to-dex.d.ts +2 -1
- package/dist/shared/foundation/gas/bundle-gas.d.ts +11 -1
- package/dist/shared/foundation/nonce/nonce-manager.js +4 -1
- package/dist/utils/airdrop-sweep.js +2 -1
- package/dist/utils/swap-helpers.js +1 -1
- package/dist/utils/wallet.js +4 -2
- package/package.json +2 -1
|
@@ -22,14 +22,14 @@ export declare function getBundleOptions(config: BundleMerkleConfigInput, blockO
|
|
|
22
22
|
maxRetries: number;
|
|
23
23
|
};
|
|
24
24
|
export declare function getGasPriceConfig(config: BundleGasConfigInput): GasPriceConfig;
|
|
25
|
-
export declare function shouldExtractProfit(_config?:
|
|
26
|
-
export declare function getProfitRateBps(_config?:
|
|
27
|
-
export declare function getProfitRecipient(_config?:
|
|
28
|
-
export declare function calculateProfit(amount: bigint, _config?:
|
|
25
|
+
export declare function shouldExtractProfit(_config?: BundleMerkleConfigInput): boolean;
|
|
26
|
+
export declare function getProfitRateBps(_config?: BundleMerkleConfigInput): number;
|
|
27
|
+
export declare function getProfitRecipient(_config?: BundleMerkleConfigInput): string;
|
|
28
|
+
export declare function calculateProfit(amount: bigint, _config?: BundleMerkleConfigInput): {
|
|
29
29
|
profit: bigint;
|
|
30
30
|
remaining: bigint;
|
|
31
31
|
};
|
|
32
|
-
export declare function calculateBatchProfit(amounts: bigint[], _config?:
|
|
32
|
+
export declare function calculateBatchProfit(amounts: bigint[], _config?: BundleMerkleConfigInput): {
|
|
33
33
|
totalProfit: bigint;
|
|
34
34
|
remainingAmounts: bigint[];
|
|
35
35
|
};
|
|
@@ -26,7 +26,8 @@ export function getBundleErrorMessage(key, ...args) {
|
|
|
26
26
|
const lang = (process.env.LANG || process.env.LANGUAGE || 'en').toLowerCase().includes('zh') ? 'zh' : 'en';
|
|
27
27
|
const message = BUNDLE_ERRORS[key][lang];
|
|
28
28
|
if (typeof message === 'function') {
|
|
29
|
-
|
|
29
|
+
const [a, b] = args;
|
|
30
|
+
return message(a, b);
|
|
30
31
|
}
|
|
31
32
|
return message;
|
|
32
33
|
}
|
|
@@ -101,7 +101,7 @@ export type BalanceValidationParams = {
|
|
|
101
101
|
buyerAddress?: string;
|
|
102
102
|
};
|
|
103
103
|
export declare function validateFinalBalances({ sameAddress, buyerBalance, buyAmountBNB, reserveGas, gasLimit, gasPrice, useNativeToken, quoteTokenDecimals, provider, buyerAddress, }: BalanceValidationParams): Promise<void>;
|
|
104
|
-
export declare function countTruthy(values:
|
|
104
|
+
export declare function countTruthy(values: Array<string | number | bigint | boolean | null | undefined>): number;
|
|
105
105
|
/**
|
|
106
106
|
* PancakeSwap V2/V3 通用捆绑换手(Merkle Bundle)
|
|
107
107
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - 查询: 通过 projectId / token 地址获取信息
|
|
7
7
|
*/
|
|
8
8
|
import { JsonRpcProvider } from 'ethers';
|
|
9
|
-
import type { IroCreateParams, IroCreateProjectResult, IroQueryConfig, IroFactoryTokenInfo } from './types.js';
|
|
9
|
+
import type { IroCreateParams, IroCreateProjectResult, IroQueryConfig, IroFactoryTokenInfo, IroCreateProjectParams } from './types.js';
|
|
10
10
|
export declare class IroFactoryQuery {
|
|
11
11
|
private provider;
|
|
12
12
|
private factory;
|
|
@@ -31,7 +31,7 @@ export declare class IroFactoryQuery {
|
|
|
31
31
|
/**
|
|
32
32
|
* 编码 createProject calldata
|
|
33
33
|
*/
|
|
34
|
-
export declare function encodeCreateProjectCall(params:
|
|
34
|
+
export declare function encodeCreateProjectCall(params: IroCreateProjectParams): string;
|
|
35
35
|
/**
|
|
36
36
|
* 创建 IRO 项目 — 签名交易
|
|
37
37
|
*
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 各钱包离线签署操作意图,主钱包收集后统一提交。
|
|
5
5
|
*/
|
|
6
|
-
import { Wallet, JsonRpcProvider } from 'ethers';
|
|
6
|
+
import { Wallet, JsonRpcProvider, Interface } from 'ethers';
|
|
7
7
|
import { ENI_CHAIN_ID, ENI_RPC_URL } from '../constants.js';
|
|
8
8
|
import { ENI_BUNDLER_ABI, EIP712_DOMAIN, SIGNED_OP_TYPE, TOKEN_PULL_TYPE } from './constants.js';
|
|
9
9
|
const DEFAULT_DEADLINE_SEC = 300;
|
|
@@ -19,7 +19,6 @@ function buildDomain(bundlerAddress, chainId = Number(ENI_CHAIN_ID)) {
|
|
|
19
19
|
*/
|
|
20
20
|
export async function getBundlerNonce(params) {
|
|
21
21
|
const provider = new JsonRpcProvider(params.rpcUrl ?? ENI_RPC_URL, ENI_CHAIN_ID, { staticNetwork: true });
|
|
22
|
-
const { Interface } = await import('ethers');
|
|
23
22
|
const iface = new Interface(ENI_BUNDLER_ABI);
|
|
24
23
|
const data = iface.encodeFunctionData('nonces', [params.signer]);
|
|
25
24
|
const result = await provider.call({ to: params.bundlerAddress, data });
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* 提供代币 Logo、社交链接、描述等元信息查询
|
|
6
6
|
*/
|
|
7
7
|
import { Contract, JsonRpcProvider } from 'ethers';
|
|
8
|
-
import { ENI_CHAIN_ID, ENI_RPC_URL } from '../../constants.js';
|
|
8
|
+
import { ENI_CHAIN_ID, ENI_RPC_URL, LP_FAIR_LAUNCHER_ABI } from '../../constants.js';
|
|
9
|
+
import { LP_FAIR_LAUNCHER_ADDRESS } from '../fair-launch/constants.js';
|
|
9
10
|
import { ERC20_ABI } from '../../../../abis/common.js';
|
|
10
11
|
/**
|
|
11
12
|
* 从 ERC20 合约获取 DAOaaS Portal 代币基本元数据
|
|
@@ -41,8 +42,6 @@ export async function getTokenMeta(token, rpcUrl = ENI_RPC_URL) {
|
|
|
41
42
|
* FairLaunch 代币的 logo 和 metadata 存储在 LaunchParams 中
|
|
42
43
|
*/
|
|
43
44
|
export async function getFairLaunchTokenMeta(token, rpcUrl = ENI_RPC_URL) {
|
|
44
|
-
const { LP_FAIR_LAUNCHER_ABI } = await import('../../constants.js');
|
|
45
|
-
const { LP_FAIR_LAUNCHER_ADDRESS } = await import('../fair-launch/constants.js');
|
|
46
45
|
const provider = new JsonRpcProvider(rpcUrl, ENI_CHAIN_ID, { staticNetwork: true });
|
|
47
46
|
const launcher = new Contract(LP_FAIR_LAUNCHER_ADDRESS, LP_FAIR_LAUNCHER_ABI, provider);
|
|
48
47
|
const info = await launcher.getTokenInfoByToken(token);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* ENI 使用 EIP-1559 (type 2) 交易,与 BSC (type 0) 不同
|
|
5
5
|
*/
|
|
6
6
|
import { JsonRpcProvider } from 'ethers';
|
|
7
|
-
import type { IroCreateParams, IroCreateProjectResult, IroQueryConfig, IroFactoryTokenInfo } from './types.js';
|
|
7
|
+
import type { IroCreateParams, IroCreateProjectResult, IroQueryConfig, IroFactoryTokenInfo, IroCreateProjectParams } from './types.js';
|
|
8
8
|
export declare class IroFactoryQuery {
|
|
9
9
|
private provider;
|
|
10
10
|
private factory;
|
|
@@ -20,7 +20,7 @@ export declare class IroFactoryQuery {
|
|
|
20
20
|
}>;
|
|
21
21
|
get providerInstance(): JsonRpcProvider;
|
|
22
22
|
}
|
|
23
|
-
export declare function encodeCreateProjectCall(params:
|
|
23
|
+
export declare function encodeCreateProjectCall(params: IroCreateProjectParams): string;
|
|
24
24
|
export declare function createProject(params: IroCreateParams): Promise<IroCreateProjectResult>;
|
|
25
25
|
export declare function parseCreateProjectEvent(receipt: {
|
|
26
26
|
logs: Array<{
|
|
@@ -237,23 +237,12 @@ export async function bundleSell(params) {
|
|
|
237
237
|
// 判断是否需要查询代币余额
|
|
238
238
|
const needBalanceQuery = !sellAmounts || sellAmounts.length !== wallets.length;
|
|
239
239
|
// 构建并行请求
|
|
240
|
-
const
|
|
241
|
-
// 1. 批量获取授权钱包的 nonce
|
|
240
|
+
const [walletNonces, mainNonce, feeData, tokenBalances] = await Promise.all([
|
|
242
241
|
batchGetNonces(allAddresses, provider),
|
|
243
|
-
// 2. 获取主钱包的交易 nonce
|
|
244
242
|
provider.getTransactionCount(mainWallet.address, 'pending'),
|
|
245
|
-
// 3. 获取 gas 费用数据
|
|
246
243
|
provider.getFeeData(),
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (needBalanceQuery) {
|
|
250
|
-
parallelPromises.push(getTokenBalances(wallets, tokenAddress));
|
|
251
|
-
}
|
|
252
|
-
const results = await Promise.all(parallelPromises);
|
|
253
|
-
const walletNonces = results[0];
|
|
254
|
-
const mainNonce = results[1];
|
|
255
|
-
const feeData = results[2];
|
|
256
|
-
const tokenBalances = needBalanceQuery ? results[3] : null;
|
|
244
|
+
needBalanceQuery ? getTokenBalances(wallets, tokenAddress) : Promise.resolve(null),
|
|
245
|
+
]);
|
|
257
246
|
// ========================================
|
|
258
247
|
// 同步计算卖出数量
|
|
259
248
|
// ========================================
|
|
@@ -18,7 +18,7 @@ import { UserType } from './utils.js';
|
|
|
18
18
|
export interface TransferResult {
|
|
19
19
|
signedTransaction: string;
|
|
20
20
|
hopWallets?: GeneratedWallet[];
|
|
21
|
-
metadata: Record<string,
|
|
21
|
+
metadata: Record<string, string | number | boolean | bigint | null | undefined>;
|
|
22
22
|
}
|
|
23
23
|
export interface DisperseParams {
|
|
24
24
|
/** 资金来源钱包私钥(Token/OKB 从这个钱包转出) */
|
|
@@ -210,22 +210,21 @@ export async function sweep(params) {
|
|
|
210
210
|
// ========================================
|
|
211
211
|
const sourceAddresses = sourceWallets.map((w) => w.address);
|
|
212
212
|
const addressesToGetNonces = payerWallet ? [payerWallet.address, ...sourceAddresses] : sourceAddresses;
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
213
|
+
const [allNoncesResult, feeData, balancesRaw] = await Promise.all([
|
|
214
|
+
batchGetNonces(addressesToGetNonces, provider),
|
|
215
|
+
provider.getFeeData(),
|
|
216
|
+
isNative
|
|
217
|
+
? Promise.all(sourceWallets.map((w) => provider.getBalance(w.address)))
|
|
218
|
+
: Promise.all(sourceWallets.map((w) => {
|
|
219
|
+
if (!tokenAddress)
|
|
220
|
+
throw new Error('tokenAddress is required for ERC20 sweep');
|
|
221
|
+
const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
|
|
222
|
+
return tokenContract.balanceOf(w.address);
|
|
223
|
+
})),
|
|
224
|
+
]);
|
|
225
225
|
const payerNonce = payerWallet ? allNoncesResult[0] : allNoncesResult[0];
|
|
226
226
|
const nonces = payerWallet ? allNoncesResult.slice(1) : allNoncesResult;
|
|
227
|
-
const
|
|
228
|
-
const balances = results[2].map((b) => typeof b === 'bigint' ? b : BigInt(b.toString()));
|
|
227
|
+
const balances = balancesRaw;
|
|
229
228
|
// 计算转账金额
|
|
230
229
|
// ✅ 修复:当 percent=100 时,不预留 gas,归集全部余额
|
|
231
230
|
const isFullSweep = percent >= 100;
|
|
@@ -7,6 +7,7 @@ import { UNIFIED_DELEGATE_ADDRESS, UNIFIED_DELEGATE_ABI, FLAP_PORTAL_ABI, FLAP_P
|
|
|
7
7
|
import { bundleBuy } from './bundle-buy.js';
|
|
8
8
|
import { bundleSell } from './bundle-sell.js';
|
|
9
9
|
import { bundleSwap } from './bundle-swap.js';
|
|
10
|
+
import { quoteV2, quoteV3 } from '../../../utils/quote-helpers.js';
|
|
10
11
|
import { truncateDecimals, } from './volume-helpers.js';
|
|
11
12
|
export async function washVolume(params) {
|
|
12
13
|
const { mainPrivateKey, privateKeys, tokenAddress, tokenDecimals = 18, buyAmountPerWallet, sellPercent = 100, poolType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, userType = 'v0', // ✅ 用户类型(影响利润率)
|
|
@@ -108,7 +109,6 @@ export async function washVolume(params) {
|
|
|
108
109
|
else if (poolType === 'V2' || poolType === 'V3') {
|
|
109
110
|
// ✅ V2/V3 外盘模式:通过报价获取预估代币数量,确保买卖对等
|
|
110
111
|
try {
|
|
111
|
-
const { quoteV2, quoteV3 } = await import('../../../utils/quote-helpers.js');
|
|
112
112
|
let totalTokenAmount = 0n;
|
|
113
113
|
if (poolType === 'V3') {
|
|
114
114
|
const result = await quoteV3(provider, WOKB_ADDRESS, tokenAddress, totalBuyWei, 'XLAYER', fee);
|
|
@@ -258,21 +258,21 @@ export async function bundleVolume(params) {
|
|
|
258
258
|
const allAddresses = allWallets.map((w) => w.address);
|
|
259
259
|
// 获取卖出钱包的代币余额(如果需要)
|
|
260
260
|
const needBalanceQuery = !sellAmounts || sellAmounts.length !== sellerWallets.length;
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
261
|
+
const tokenContract = needBalanceQuery && sellerWallets.length > 0 ? new ethers.Contract(tokenAddress, ERC20_ABI, provider) : null;
|
|
262
|
+
const [nonces, feeData, sellerBalancesRaw] = await Promise.all([
|
|
263
|
+
batchGetNonces(allAddresses, provider),
|
|
264
|
+
provider.getFeeData(),
|
|
265
|
+
needBalanceQuery && sellerWallets.length > 0 && tokenContract
|
|
266
|
+
? Promise.all(sellerWallets.map((w) => tokenContract.balanceOf(w.address)))
|
|
267
|
+
: Promise.resolve(null),
|
|
268
|
+
]);
|
|
269
269
|
// 计算卖出金额
|
|
270
270
|
let sellAmountsWei;
|
|
271
271
|
if (sellAmounts && sellAmounts.length === sellerWallets.length) {
|
|
272
272
|
// ✅ 如果有指定卖出金额,直接使用
|
|
273
273
|
sellAmountsWei = sellAmounts.map((amt) => ethers.parseUnits(truncateDecimals(amt, tokenDecimals), tokenDecimals));
|
|
274
274
|
}
|
|
275
|
-
else if (needBalanceQuery &&
|
|
275
|
+
else if (needBalanceQuery && sellerBalancesRaw) {
|
|
276
276
|
// ✅ 根据 poolType 预估买入能获得的代币数量
|
|
277
277
|
let estimatedTokenAmount = 0n;
|
|
278
278
|
try {
|
package/dist/merkle/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
export { disperseWithBundleMerkle } from '../bundle-core/four-meme/utils-disperse.js';
|
|
7
7
|
export { sweepWithBundleMerkle } from '../bundle-core/four-meme/utils-sweep.js';
|
|
8
8
|
export { pairwiseTransferWithBundleMerkle } from '../bundle-core/four-meme/utils-pairwise.js';
|
|
9
|
-
export { fourBundleSwapMerkle, fourBatchSwapMerkle, fourQuickBatchSwapMerkle
|
|
9
|
+
export { fourBundleSwapMerkle, fourBatchSwapMerkle, fourQuickBatchSwapMerkle } from '../bundle-core/four-meme/swap.js';
|
|
10
10
|
export { fourBundleBuyFirstMerkle } from '../bundle-core/four-meme/swap-buy-first.js';
|
|
11
11
|
export { submitBundleToMerkle, submitBundleToBlockRazor, submitDirectToRpc, submitDirectToRpcSmart, type MerkleSubmitConfig, type SubmitBundleResult, type BlockRazorSubmitConfig, type DirectSubmitConfig, } from '../bundle-core/submit.js';
|
|
12
12
|
export type { DisperseSignParams, DisperseMerkleResult, SweepSignParams, SweepMerkleResult, FourBundleSwapSignParams, FourSwapResult, FourBatchSwapSignParams, FourBatchSwapResult, FourQuickBatchSwapSignParams, FourQuickBatchSwapResult, PairwiseTransferSignParams, PairwiseTransferMerkleResult, } from '../bundle-core/four-meme/types.js';
|
package/dist/merkle/index.js
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
export { disperseWithBundleMerkle } from '../bundle-core/four-meme/utils-disperse.js';
|
|
7
7
|
export { sweepWithBundleMerkle } from '../bundle-core/four-meme/utils-sweep.js';
|
|
8
8
|
export { pairwiseTransferWithBundleMerkle } from '../bundle-core/four-meme/utils-pairwise.js';
|
|
9
|
-
export { fourBundleSwapMerkle, fourBatchSwapMerkle, fourQuickBatchSwapMerkle
|
|
9
|
+
export { fourBundleSwapMerkle, fourBatchSwapMerkle, fourQuickBatchSwapMerkle } from '../bundle-core/four-meme/swap.js';
|
|
10
10
|
export { fourBundleBuyFirstMerkle } from '../bundle-core/four-meme/swap-buy-first.js';
|
|
11
11
|
export { submitBundleToMerkle, submitBundleToBlockRazor, submitDirectToRpc, submitDirectToRpcSmart, } from '../bundle-core/submit.js';
|
|
@@ -87,14 +87,26 @@ export type CreateTokenReq = {
|
|
|
87
87
|
funGroup: false;
|
|
88
88
|
clickFun: false;
|
|
89
89
|
};
|
|
90
|
+
export type FourJsonValue = string | number | boolean | null | FourJsonValue[] | {
|
|
91
|
+
[key: string]: FourJsonValue;
|
|
92
|
+
};
|
|
93
|
+
export type FourPublicConfig = {
|
|
94
|
+
[key: string]: FourJsonValue;
|
|
95
|
+
};
|
|
96
|
+
export type FourTokenDetail = {
|
|
97
|
+
[key: string]: FourJsonValue;
|
|
98
|
+
} & {
|
|
99
|
+
address?: string;
|
|
100
|
+
name?: string;
|
|
101
|
+
symbol?: string;
|
|
102
|
+
};
|
|
90
103
|
export type CreateTokenResp = {
|
|
91
104
|
createArg: string;
|
|
92
105
|
signature: string;
|
|
93
106
|
tokenAddr?: string;
|
|
94
107
|
address?: string;
|
|
95
108
|
token?: string;
|
|
96
|
-
|
|
97
|
-
};
|
|
109
|
+
} & Record<string, string | undefined>;
|
|
98
110
|
export declare class FourClient {
|
|
99
111
|
private baseUrl;
|
|
100
112
|
private uploadUrls;
|
|
@@ -124,9 +136,12 @@ export declare class FourClient {
|
|
|
124
136
|
private isCorsOrNetworkError;
|
|
125
137
|
private getFilenameFromBlob;
|
|
126
138
|
createToken(accessToken: string, req: CreateTokenReq): Promise<CreateTokenResp>;
|
|
127
|
-
getPublicConfig(): Promise<
|
|
128
|
-
getTokenByAddress(address: string, accessToken?: string): Promise<
|
|
129
|
-
getTokensByAddresses(addresses: string[], accessToken?: string): Promise<
|
|
130
|
-
|
|
139
|
+
getPublicConfig(): Promise<FourPublicConfig>;
|
|
140
|
+
getTokenByAddress(address: string, accessToken?: string): Promise<FourTokenDetail>;
|
|
141
|
+
getTokensByAddresses(addresses: string[], accessToken?: string): Promise<(FourTokenDetail | {
|
|
142
|
+
address: string;
|
|
143
|
+
error: string;
|
|
144
|
+
})[]>;
|
|
145
|
+
getTokenById(id: string | number, accessToken?: string): Promise<FourTokenDetail>;
|
|
131
146
|
}
|
|
132
147
|
export declare function buildLoginMessage(nonce: string): string;
|
|
@@ -155,16 +155,17 @@ export class FourClient {
|
|
|
155
155
|
return imgUrl;
|
|
156
156
|
}
|
|
157
157
|
catch (e) {
|
|
158
|
-
|
|
158
|
+
const err = e instanceof Error ? e : new Error(getErrorMessageFromUnknown(e));
|
|
159
|
+
lastError = err;
|
|
159
160
|
const canRetry = i < this.uploadUrls.length - 1;
|
|
160
|
-
if (this.isRateLimitError(
|
|
161
|
+
if (this.isRateLimitError(err) && canRetry) {
|
|
161
162
|
// IP 限流 → 切下一个端点
|
|
162
163
|
const nextUrl = this.uploadUrls[i + 1];
|
|
163
164
|
const nextLabel = nextUrl === FOUR_MEME_OFFICIAL_API ? '官方直连' : `备用端点 #${i + 2}`;
|
|
164
165
|
console.warn(`[FourClient] 端点 ${baseUrl} 触发 IP 限流(100次/天),切换到${nextLabel}...`);
|
|
165
166
|
continue;
|
|
166
167
|
}
|
|
167
|
-
if (this.isCorsOrNetworkError(
|
|
168
|
+
if (this.isCorsOrNetworkError(err) && canRetry) {
|
|
168
169
|
// CORS/网络错误(常见于浏览器直连官方 API)→ 跳过,继续尝试下一个
|
|
169
170
|
console.warn(`[FourClient] 端点 ${baseUrl} 请求失败(${isOfficial ? 'CORS限制' : '网络错误'}),跳过`);
|
|
170
171
|
continue;
|
|
@@ -251,7 +252,7 @@ export class FourClient {
|
|
|
251
252
|
}
|
|
252
253
|
async getPublicConfig() {
|
|
253
254
|
const r = await fetch(`${this.baseUrl}/v1/public/config`);
|
|
254
|
-
return await r.json();
|
|
255
|
+
return (await r.json());
|
|
255
256
|
}
|
|
256
257
|
async getTokenByAddress(address, accessToken) {
|
|
257
258
|
const r = await fetch(`${this.baseUrl}/v1/private/token/get?address=${address}`, {
|
|
@@ -261,12 +262,12 @@ export class FourClient {
|
|
|
261
262
|
if (j.code && j.code !== '0' && j.code !== 0) {
|
|
262
263
|
throw new Error(`getTokenByAddress failed: ${JSON.stringify(j)}`);
|
|
263
264
|
}
|
|
264
|
-
return j.data ?? j;
|
|
265
|
+
return (j.data ?? j);
|
|
265
266
|
}
|
|
266
267
|
async getTokensByAddresses(addresses, accessToken) {
|
|
267
268
|
if (!Array.isArray(addresses) || addresses.length === 0)
|
|
268
269
|
return [];
|
|
269
|
-
const tasks = addresses.map((addr) => this.getTokenByAddress(addr, accessToken).catch((e) => ({ address: addr, error:
|
|
270
|
+
const tasks = addresses.map((addr) => this.getTokenByAddress(addr, accessToken).catch((e) => ({ address: addr, error: getErrorMessageFromUnknown(e) })));
|
|
270
271
|
return await Promise.all(tasks);
|
|
271
272
|
}
|
|
272
273
|
async getTokenById(id, accessToken) {
|
|
@@ -277,7 +278,7 @@ export class FourClient {
|
|
|
277
278
|
if (j.code && j.code !== '0' && j.code !== 0) {
|
|
278
279
|
throw new Error(`getTokenById failed: ${JSON.stringify(j)}`);
|
|
279
280
|
}
|
|
280
|
-
return j.data ?? j;
|
|
281
|
+
return (j.data ?? j);
|
|
281
282
|
}
|
|
282
283
|
}
|
|
283
284
|
export function buildLoginMessage(nonce) {
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { JsonRpcProvider } from 'ethers';
|
|
2
2
|
export type FlapChain = 'BSC' | 'BASE' | 'XLAYER' | 'MORPH';
|
|
3
|
+
/** JSON 可序列化值(递归) */
|
|
4
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
5
|
+
[key: string]: JsonValue;
|
|
6
|
+
};
|
|
3
7
|
/** IPFS 元数据 JSON(字段因项目而异) */
|
|
4
|
-
export type FlapTokenMetaJson =
|
|
8
|
+
export type FlapTokenMetaJson = {
|
|
9
|
+
[key: string]: JsonValue;
|
|
10
|
+
};
|
|
5
11
|
export type FlapMetaByAddressResult = {
|
|
6
12
|
cid: string;
|
|
7
13
|
data?: FlapTokenMetaJson;
|
|
@@ -2,25 +2,34 @@ export type PinataConfig = {
|
|
|
2
2
|
jwt: string;
|
|
3
3
|
gateway?: string;
|
|
4
4
|
};
|
|
5
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
6
|
+
[key: string]: JsonValue;
|
|
7
|
+
};
|
|
8
|
+
/** Pinata gateway JSON 响应(结构因 CID 内容而异) */
|
|
9
|
+
export type PinataGatewayJson = {
|
|
10
|
+
[key: string]: JsonValue;
|
|
11
|
+
};
|
|
5
12
|
type PinataPinApiResponse = {
|
|
6
13
|
IpfsHash?: string;
|
|
7
14
|
cid?: string;
|
|
8
15
|
PinSize?: number;
|
|
9
16
|
Timestamp?: string;
|
|
10
17
|
};
|
|
18
|
+
export type PinataJsonUpload = {
|
|
19
|
+
[key: string]: JsonValue;
|
|
20
|
+
};
|
|
11
21
|
export declare class PinataClient {
|
|
12
22
|
private client;
|
|
13
|
-
private ready;
|
|
14
23
|
constructor(cfg: PinataConfig);
|
|
15
24
|
uploadFile(file: File): Promise<{
|
|
16
25
|
cid: string;
|
|
17
26
|
id: string;
|
|
18
27
|
}>;
|
|
19
|
-
uploadJSON(obj:
|
|
28
|
+
uploadJSON(obj: PinataJsonUpload, name?: string): Promise<{
|
|
20
29
|
cid: string;
|
|
21
30
|
id: string;
|
|
22
31
|
}>;
|
|
23
|
-
get(cid: string): Promise<
|
|
32
|
+
get(cid: string): Promise<PinataGatewayJson>;
|
|
24
33
|
url(cid: string): Promise<string>;
|
|
25
34
|
}
|
|
26
35
|
export type PinataPinResp = {
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import NodeFormData from 'form-data';
|
|
4
|
+
import { PinataSDK } from 'pinata';
|
|
1
5
|
export class PinataClient {
|
|
2
6
|
client;
|
|
3
|
-
ready;
|
|
4
7
|
constructor(cfg) {
|
|
5
|
-
this.
|
|
6
|
-
const mod = (await import('pinata'));
|
|
7
|
-
this.client = new mod.PinataSDK({ pinataJwt: cfg.jwt, pinataGateway: cfg.gateway });
|
|
8
|
-
})();
|
|
8
|
+
this.client = new PinataSDK({ pinataJwt: cfg.jwt, pinataGateway: cfg.gateway });
|
|
9
9
|
}
|
|
10
10
|
async uploadFile(file) {
|
|
11
|
-
await this.ready;
|
|
12
11
|
const r = await this.client.upload.file(file);
|
|
13
12
|
return { cid: r.cid, id: r.id ?? '' };
|
|
14
13
|
}
|
|
@@ -18,11 +17,9 @@ export class PinataClient {
|
|
|
18
17
|
return await this.uploadFile(file);
|
|
19
18
|
}
|
|
20
19
|
async get(cid) {
|
|
21
|
-
await this.ready;
|
|
22
20
|
return await this.client.gateways.public.get(cid);
|
|
23
21
|
}
|
|
24
22
|
async url(cid) {
|
|
25
|
-
await this.ready;
|
|
26
23
|
return await this.client.gateways.convert(cid);
|
|
27
24
|
}
|
|
28
25
|
}
|
|
@@ -34,16 +31,9 @@ export class PinataClient {
|
|
|
34
31
|
* @param fileName 可选:内存数据的文件名
|
|
35
32
|
*/
|
|
36
33
|
export async function pinFileToIPFSWithJWT(jwt, filePath, data, fileName) {
|
|
37
|
-
const
|
|
38
|
-
const FormDataMod = (await import('form-data'));
|
|
39
|
-
const fsMod = await import('node:fs');
|
|
40
|
-
const FormDataCtor = FormDataMod.default;
|
|
41
|
-
if (!FormDataCtor) {
|
|
42
|
-
throw new Error('pinFileToIPFSWithJWT: form-data module unavailable');
|
|
43
|
-
}
|
|
44
|
-
const form = new FormDataCtor();
|
|
34
|
+
const form = new NodeFormData();
|
|
45
35
|
if (filePath && filePath.length > 0) {
|
|
46
|
-
form.append('file',
|
|
36
|
+
form.append('file', createReadStream(filePath));
|
|
47
37
|
}
|
|
48
38
|
else if (data) {
|
|
49
39
|
form.append('file', data, { filename: fileName || 'upload.bin' });
|
|
@@ -51,8 +41,8 @@ export async function pinFileToIPFSWithJWT(jwt, filePath, data, fileName) {
|
|
|
51
41
|
else {
|
|
52
42
|
throw new Error('pinFileToIPFSWithJWT: either filePath or data must be provided');
|
|
53
43
|
}
|
|
54
|
-
const resp = await
|
|
55
|
-
headers: { Authorization: `Bearer ${jwt}`, ...
|
|
44
|
+
const resp = await axios.post('https://api.pinata.cloud/pinning/pinFileToIPFS', form, {
|
|
45
|
+
headers: { Authorization: `Bearer ${jwt}`, ...form.getHeaders() },
|
|
56
46
|
maxContentLength: Infinity,
|
|
57
47
|
maxBodyLength: Infinity,
|
|
58
48
|
});
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 3. 买到毕业(代币上 DEX)
|
|
8
8
|
* 4. 多个钱包在 PancakeSwap(外盘)继续买入
|
|
9
9
|
*/
|
|
10
|
+
import { type TaxVaultConfig } from '../vault.js';
|
|
10
11
|
import { type FlapSignConfig } from './config.js';
|
|
11
12
|
import type { MerkleSignedResult } from './types.js';
|
|
12
13
|
export type CreateToDexChain = 'bsc' | 'xlayer' | 'base';
|
|
@@ -85,7 +86,7 @@ export interface FlapCreateToDexParams {
|
|
|
85
86
|
lpBps: number;
|
|
86
87
|
minimumShareBalance?: number;
|
|
87
88
|
/** ✅ Tax Vault 金库配置(可选) */
|
|
88
|
-
vaultConfig?:
|
|
89
|
+
vaultConfig?: TaxVaultConfig;
|
|
89
90
|
};
|
|
90
91
|
/** V3 扩展 ID */
|
|
91
92
|
extensionID?: string;
|
|
@@ -36,4 +36,14 @@ export interface CommonBundleConfig {
|
|
|
36
36
|
export declare function getGasLimit(config: CommonBundleConfig, defaultGas?: number): bigint;
|
|
37
37
|
export declare function getGasPriceConfig(config: CommonBundleConfig): GasPriceConfig;
|
|
38
38
|
export declare function getTxType(config: CommonBundleConfig): 0 | 2;
|
|
39
|
-
export
|
|
39
|
+
export type LegacyGasFields = {
|
|
40
|
+
type: 0;
|
|
41
|
+
gasPrice: bigint;
|
|
42
|
+
};
|
|
43
|
+
export type Eip1559GasFields = {
|
|
44
|
+
type: 2;
|
|
45
|
+
maxFeePerGas: bigint;
|
|
46
|
+
maxPriorityFeePerGas: bigint;
|
|
47
|
+
};
|
|
48
|
+
export type TxGasFields = LegacyGasFields | Eip1559GasFields;
|
|
49
|
+
export declare function buildGasFields(txType: number, gasPrice: bigint): TxGasFields;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import '../tx/wallet-sign-patch.js';
|
|
2
2
|
import { CHAINS } from '../../constants/index.js';
|
|
3
|
+
function asBatchSendProvider(provider) {
|
|
4
|
+
return provider;
|
|
5
|
+
}
|
|
3
6
|
const GLOBAL_NONCE_TTL_MS = 60 * 1000;
|
|
4
7
|
const globalNonceCursorByChain = new Map();
|
|
5
8
|
function isFlakyNonceChain(chainId) {
|
|
@@ -145,7 +148,7 @@ export class NonceManager {
|
|
|
145
148
|
id: idx + 1,
|
|
146
149
|
jsonrpc: '2.0',
|
|
147
150
|
}));
|
|
148
|
-
const rawResults = await this.provider._send(batchRequests);
|
|
151
|
+
const rawResults = await asBatchSendProvider(this.provider)._send(batchRequests);
|
|
149
152
|
queryResults = rawResults.map((r) => {
|
|
150
153
|
if (r.result) {
|
|
151
154
|
return parseInt(r.result, 16);
|
|
@@ -146,7 +146,8 @@ export async function sweepWithBundle(params) {
|
|
|
146
146
|
try {
|
|
147
147
|
const balData = iface.encodeFunctionData('balanceOf', [w.address]);
|
|
148
148
|
const balRaw = await provider.call({ to: tokenAddress, data: balData });
|
|
149
|
-
const
|
|
149
|
+
const decoded = iface.decodeFunctionResult('balanceOf', balRaw);
|
|
150
|
+
const bal = decoded[0];
|
|
150
151
|
return bal;
|
|
151
152
|
}
|
|
152
153
|
catch {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { ethers, Contract } from 'ethers';
|
|
5
5
|
import { batchCheckAllowances } from './erc20.js';
|
|
6
6
|
import { ERC20_ABI } from '../abis/common.js';
|
|
7
|
+
import { NonceManager, getOptimizedGasPrice } from './bundle-helpers.js';
|
|
7
8
|
const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
|
|
8
9
|
/**
|
|
9
10
|
* 获取 Gas Limit(授权专用)
|
|
@@ -35,7 +36,6 @@ export async function ensureTokenApproval(provider, merkle, wallet, tokenAddress
|
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
// ✅ 使用 NonceManager(与 pancake-proxy.ts 一致)
|
|
38
|
-
const { NonceManager, getOptimizedGasPrice } = await import('./bundle-helpers.js');
|
|
39
39
|
const nonceManager = new NonceManager(provider);
|
|
40
40
|
// 构建授权交易
|
|
41
41
|
const tokenContract = new Contract(tokenAddress, ERC20_ABI, wallet);
|
package/dist/utils/wallet.js
CHANGED
|
@@ -120,7 +120,8 @@ async function _queryMultiTokenBalancesSingle(provider, multicallAddress, tokenA
|
|
|
120
120
|
// ✅ 单次 multicall 获取所有数据
|
|
121
121
|
const callData = multiIface.encodeFunctionData('aggregate', [calls]);
|
|
122
122
|
const result = await provider.call({ to: multicallAddress, data: callData });
|
|
123
|
-
const
|
|
123
|
+
const decoded = multiIface.decodeFunctionResult('aggregate', result);
|
|
124
|
+
const returnData = decoded[1];
|
|
124
125
|
// ✅ 并行解码结果
|
|
125
126
|
const outputs = holders.map((addr) => ({
|
|
126
127
|
address: addr,
|
|
@@ -213,7 +214,8 @@ async function _querySingleTokenBalancesSingle(provider, multicallAddress, token
|
|
|
213
214
|
}));
|
|
214
215
|
const callData = multiIface.encodeFunctionData('aggregate', [calls]);
|
|
215
216
|
const result = await provider.call({ to: multicallAddress, data: callData });
|
|
216
|
-
const
|
|
217
|
+
const decoded = multiIface.decodeFunctionResult('aggregate', result);
|
|
218
|
+
const returnData = decoded[1];
|
|
217
219
|
return holders.map((addr, i) => {
|
|
218
220
|
try {
|
|
219
221
|
const decoded = erc20Iface.decodeFunctionResult('balanceOf', returnData[i]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "four-flap-meme-sdk",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "SDK for Flap bonding curve and four.meme TokenManager",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -182,6 +182,7 @@
|
|
|
182
182
|
"@ethereumjs/rlp": "^10.1.0",
|
|
183
183
|
"axios": "^1.12.2",
|
|
184
184
|
"ethers": "^6.16.0",
|
|
185
|
+
"form-data": "^4.0.2",
|
|
185
186
|
"pinata": "^1.10.1"
|
|
186
187
|
},
|
|
187
188
|
"devDependencies": {
|