four-flap-meme-sdk 4.3.5 → 4.3.7
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/core/clients/four-client-fallback.test.js +24 -0
- package/dist/core/clients/four.js +8 -12
- package/dist/domains/flap/portal-create-token.js +15 -9
- package/dist/domains/flap/portal-create-token.test.d.ts +1 -0
- package/dist/domains/flap/portal-create-token.test.js +32 -0
- package/dist/domains/lp-inspect/inspect-tax.test.js +9 -0
- package/dist/domains/lp-inspect/inspect.js +5 -3
- package/package.json +1 -1
|
@@ -24,6 +24,30 @@ describe('FourClient private API rate-limit fallback', () => {
|
|
|
24
24
|
expect(nonce).toBe('nonce-1');
|
|
25
25
|
expect(calls.some((url) => url.includes('four.meme'))).toBe(true);
|
|
26
26
|
});
|
|
27
|
+
it('retries generateNonce on bscfourapi after custom baseUrl -1013', async () => {
|
|
28
|
+
const calls = [];
|
|
29
|
+
vi.stubGlobal('fetch', vi.fn(async (input) => {
|
|
30
|
+
const url = String(input);
|
|
31
|
+
calls.push(url);
|
|
32
|
+
if (url.includes('example.com')) {
|
|
33
|
+
return new Response(JSON.stringify({ code: -1013, msg: 'Too many requests, banned for 15 minutes, remain 493s' }), { status: 429 });
|
|
34
|
+
}
|
|
35
|
+
if (url.includes('bscfourapi.emit.tools')) {
|
|
36
|
+
return new Response(JSON.stringify({ code: 0, data: 'nonce-proxy' }));
|
|
37
|
+
}
|
|
38
|
+
return new Response(JSON.stringify({ code: -1013, msg: 'Too many requests, banned for 15 minutes' }), {
|
|
39
|
+
status: 429,
|
|
40
|
+
});
|
|
41
|
+
}));
|
|
42
|
+
const nonce = await new FourClient({ baseUrl: 'https://example.com/four-meme-api' }).generateNonce({
|
|
43
|
+
accountAddress: `0x${'11'.repeat(20)}`,
|
|
44
|
+
verifyType: 'LOGIN',
|
|
45
|
+
networkCode: 'BSC',
|
|
46
|
+
});
|
|
47
|
+
expect(nonce).toBe('nonce-proxy');
|
|
48
|
+
expect(calls.some((url) => url.includes('example.com'))).toBe(true);
|
|
49
|
+
expect(calls.some((url) => url.includes('bscfourapi.emit.tools'))).toBe(true);
|
|
50
|
+
});
|
|
27
51
|
it('retries generateNonce when the proxy returns HTML', async () => {
|
|
28
52
|
vi.stubGlobal('fetch', vi.fn(async (input) => {
|
|
29
53
|
const url = String(input);
|
|
@@ -109,6 +109,8 @@ async function hashBlob(blob) {
|
|
|
109
109
|
}
|
|
110
110
|
/** Four.meme 限流:-1038 每日上传配额,-1013 15 分钟封禁 */
|
|
111
111
|
const RATE_LIMIT_CODES = new Set([-1038, -1013]);
|
|
112
|
+
/** Cloudflare Worker 代理(独立出口 IP,主线路被封时换这条) */
|
|
113
|
+
const FOUR_MEME_PROXY_API = 'https://bscfourapi.emit.tools';
|
|
112
114
|
/**
|
|
113
115
|
* Four.meme 官方 API。nonce/login 浏览器可直连;create/upload 需 `meme-web-access`,
|
|
114
116
|
* 官方 CORS 不放行该头,浏览器应走自有代理。
|
|
@@ -119,17 +121,11 @@ export class FourClient {
|
|
|
119
121
|
uploadUrls;
|
|
120
122
|
constructor(cfg) {
|
|
121
123
|
// 默认使用 Cloudflare Workers 代理,已配置 CORS,浏览器可直接使用
|
|
122
|
-
this.baseUrl = cfg?.baseUrl ||
|
|
123
|
-
//
|
|
124
|
-
const fallbacks = cfg?.uploadFallbackUrls || [];
|
|
124
|
+
this.baseUrl = cfg?.baseUrl || FOUR_MEME_PROXY_API;
|
|
125
|
+
// 主 URL → 自定义备用 → emit 代理(换 IP)→ 官方直连(Node 无 CORS)
|
|
125
126
|
this.uploadUrls = [
|
|
126
|
-
this.baseUrl,
|
|
127
|
-
...fallbacks,
|
|
128
|
-
// ✅ 官方 API 始终作为最后兜底(Node.js 环境无 CORS 限制,走用户自己 IP)
|
|
129
|
-
FOUR_MEME_OFFICIAL_API,
|
|
127
|
+
...new Set([this.baseUrl, ...(cfg?.uploadFallbackUrls || []), FOUR_MEME_PROXY_API, FOUR_MEME_OFFICIAL_API]),
|
|
130
128
|
];
|
|
131
|
-
// 去重(如果用户 baseUrl 就是官方地址,避免重复请求)
|
|
132
|
-
this.uploadUrls = [...new Set(this.uploadUrls)];
|
|
133
129
|
}
|
|
134
130
|
attachApiCode(err, code) {
|
|
135
131
|
const n = Number(code);
|
|
@@ -287,7 +283,7 @@ export class FourClient {
|
|
|
287
283
|
});
|
|
288
284
|
}
|
|
289
285
|
async getPublicConfig() {
|
|
290
|
-
const urls =
|
|
286
|
+
const urls = this.uploadUrls;
|
|
291
287
|
let last;
|
|
292
288
|
for (const url of urls) {
|
|
293
289
|
try {
|
|
@@ -344,7 +340,7 @@ export class FourClient {
|
|
|
344
340
|
return (j.data ?? j);
|
|
345
341
|
}
|
|
346
342
|
async searchTokenTemplates() {
|
|
347
|
-
const urls =
|
|
343
|
+
const urls = this.uploadUrls;
|
|
348
344
|
let last = [];
|
|
349
345
|
for (const url of urls) {
|
|
350
346
|
try {
|
|
@@ -374,7 +370,7 @@ export class FourClient {
|
|
|
374
370
|
return list.filter((t) => String(t.status || '').toUpperCase() === 'PUBLISHED');
|
|
375
371
|
}
|
|
376
372
|
async getTokenTemplateConfig(templateId) {
|
|
377
|
-
const urls =
|
|
373
|
+
const urls = this.uploadUrls;
|
|
378
374
|
let last = [];
|
|
379
375
|
for (const url of urls) {
|
|
380
376
|
try {
|
|
@@ -97,7 +97,12 @@ function resolveCreateQuoteAmt(params) {
|
|
|
97
97
|
function resolveUseV7Portal(params) {
|
|
98
98
|
return !!(params.useV7Portal ?? params.useV4Portal ?? params.useClInfinityLaunch);
|
|
99
99
|
}
|
|
100
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* 标准 V3 / TOKEN_V3_PERMIT:对齐 Flap 官网 live newTokenV7。
|
|
102
|
+
* 主网 Portal v5.23.1 拒 MARKETING_OR_VAULT(NewTokenV7RuleViolation(8)),
|
|
103
|
+
* 只收单槽 DIVIDEND 10000 bps、dividendToken == quoteToken(原生为 address(0))。
|
|
104
|
+
*/
|
|
105
|
+
const V7_DIVIDEND_MIN_SHARE = 10000n * 10n ** 18n;
|
|
101
106
|
function buildNewTokenV7PermitParams(params) {
|
|
102
107
|
const emptyFeeConfig = {
|
|
103
108
|
feeType: FeeType.NONE,
|
|
@@ -106,12 +111,13 @@ function buildNewTokenV7PermitParams(params) {
|
|
|
106
111
|
dividendToken: ZeroAddress,
|
|
107
112
|
minimumShareBalance: 0n,
|
|
108
113
|
};
|
|
109
|
-
const
|
|
110
|
-
|
|
114
|
+
const quoteToken = params.quoteToken || ZERO_ADDRESS;
|
|
115
|
+
const dividendFeeConfig = {
|
|
116
|
+
feeType: FeeType.DIVIDEND,
|
|
111
117
|
bps: 10000,
|
|
112
|
-
marketingAddress:
|
|
113
|
-
dividendToken:
|
|
114
|
-
minimumShareBalance:
|
|
118
|
+
marketingAddress: ZeroAddress,
|
|
119
|
+
dividendToken: quoteToken,
|
|
120
|
+
minimumShareBalance: V7_DIVIDEND_MIN_SHARE,
|
|
115
121
|
};
|
|
116
122
|
return {
|
|
117
123
|
name: params.tokenInfo.name,
|
|
@@ -120,7 +126,7 @@ function buildNewTokenV7PermitParams(params) {
|
|
|
120
126
|
dexThresh: (params.dexThresh ?? 1) & 0xff,
|
|
121
127
|
salt: params.salt ?? zeroBytes32(),
|
|
122
128
|
migratorType: MigratorType.PCS_INFINITY_CL_MIGRATOR,
|
|
123
|
-
quoteToken
|
|
129
|
+
quoteToken,
|
|
124
130
|
quoteAmt: resolveCreateQuoteAmt(params),
|
|
125
131
|
permitData: '0x',
|
|
126
132
|
extensionID: params.extensionID ?? zeroBytes32(),
|
|
@@ -132,7 +138,7 @@ function buildNewTokenV7PermitParams(params) {
|
|
|
132
138
|
antiFarmerDuration: 0n,
|
|
133
139
|
commissionReceiver: ZeroAddress,
|
|
134
140
|
tokenVersion: TokenVersion.TOKEN_V3_PERMIT,
|
|
135
|
-
feeConfigs: [
|
|
141
|
+
feeConfigs: [dividendFeeConfig, emptyFeeConfig, emptyFeeConfig, emptyFeeConfig],
|
|
136
142
|
};
|
|
137
143
|
}
|
|
138
144
|
export async function populateCreateTokenTransaction(params) {
|
|
@@ -197,7 +203,7 @@ export async function populateCreateTokenTransaction(params) {
|
|
|
197
203
|
sellTaxRate,
|
|
198
204
|
}, { mktBps: 10000, deflationBps: 0, dividendBps: 0, lpBps: 0 }));
|
|
199
205
|
}
|
|
200
|
-
// 3) 标准 V3 / TOKEN_V3_PERMIT(newTokenV7 —
|
|
206
|
+
// 3) 标准 V3 / TOKEN_V3_PERMIT(newTokenV7 — 对齐官网 live 单槽 DIVIDEND;V4 已 FeatureDisabled)
|
|
201
207
|
if (resolveUseV7Portal(params)) {
|
|
202
208
|
return params.originalPortal.newTokenV7.populateTransaction(buildNewTokenV7PermitParams(params));
|
|
203
209
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Contract, Interface, Wallet, ZeroAddress } from 'ethers';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { FLAP_PORTAL_ABI } from '../../abis/flap/portal-merkle.js';
|
|
4
|
+
import { ZERO_ADDRESS } from '../../core/constants/index.js';
|
|
5
|
+
import { FeeType, MigratorType, TokenVersion } from './portal.js';
|
|
6
|
+
import { populateCreateTokenTransaction } from './portal-create-token.js';
|
|
7
|
+
describe('populateCreateTokenTransaction V7 tax-free', () => {
|
|
8
|
+
it('uses DIVIDEND 10000 bps like flap.sh, not MARKETING_OR_VAULT', async () => {
|
|
9
|
+
const wallet = Wallet.createRandom();
|
|
10
|
+
const portal = new Contract(ZeroAddress, FLAP_PORTAL_ABI, wallet);
|
|
11
|
+
const tx = await populateCreateTokenTransaction({
|
|
12
|
+
chain: 'BSC',
|
|
13
|
+
devWallet: wallet,
|
|
14
|
+
originalPortal: portal,
|
|
15
|
+
tokenInfo: { name: 'zzz', symbol: 'zzz', meta: 'cid' },
|
|
16
|
+
beneficiary: wallet.address,
|
|
17
|
+
useV7Portal: true,
|
|
18
|
+
});
|
|
19
|
+
const parsed = new Interface(FLAP_PORTAL_ABI).parseTransaction({ data: tx.data });
|
|
20
|
+
expect(parsed?.name).toBe('newTokenV7');
|
|
21
|
+
const p = parsed.args[0];
|
|
22
|
+
expect(Number(p[5])).toBe(MigratorType.PCS_INFINITY_CL_MIGRATOR);
|
|
23
|
+
expect(Number(p[17])).toBe(TokenVersion.TOKEN_V3_PERMIT);
|
|
24
|
+
expect(p[16]).toBe(ZERO_ADDRESS);
|
|
25
|
+
const fee0 = p[18][0];
|
|
26
|
+
expect(Number(fee0[0])).toBe(FeeType.DIVIDEND);
|
|
27
|
+
expect(Number(fee0[1])).toBe(10000);
|
|
28
|
+
expect(fee0[2]).toBe(ZERO_ADDRESS);
|
|
29
|
+
expect(fee0[3]).toBe(ZERO_ADDRESS);
|
|
30
|
+
expect(fee0[4]).toBe(10000n * 10n ** 18n);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -63,4 +63,13 @@ describe('flapV8TaxBps', () => {
|
|
|
63
63
|
sellTaxRate: 500n,
|
|
64
64
|
}))).toBe(500);
|
|
65
65
|
});
|
|
66
|
+
it('keeps graduated flap 1 percent tax', () => {
|
|
67
|
+
expect(flapV8TaxBps(v8({
|
|
68
|
+
status: TokenStatus.DEX,
|
|
69
|
+
pool: '0x17Ae5fb34fE0198Ecac25f210B25bA67A2d6a746',
|
|
70
|
+
quoteTokenAddress: '0xbe9D156892E55e7154BcD3cB0FEA677F9D3103E1',
|
|
71
|
+
buyTaxRate: 100n,
|
|
72
|
+
sellTaxRate: 100n,
|
|
73
|
+
}))).toBe(100);
|
|
74
|
+
});
|
|
66
75
|
});
|
|
@@ -342,13 +342,15 @@ export async function inspectTokenLP(token, opts) {
|
|
|
342
342
|
if (opts.flapChain === 'BSC') {
|
|
343
343
|
const stV8 = await flap.getTokenV8Safe(token);
|
|
344
344
|
taxRate = flapV8TaxBps(stV8);
|
|
345
|
+
// V8 已把「无池残留 100」收成 0;毕业盘真实 1%(100 bps)要留下
|
|
346
|
+
if (taxRate > 0)
|
|
347
|
+
result.taxRate = taxRate;
|
|
345
348
|
}
|
|
346
349
|
else {
|
|
347
350
|
const stV7 = await flap.getTokenV7(token);
|
|
348
351
|
taxRate = Number(stV7.taxRate ?? 0n);
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
result.taxRate = taxRate;
|
|
352
|
+
if (taxRate > 0 && taxRate !== 100)
|
|
353
|
+
result.taxRate = taxRate;
|
|
352
354
|
}
|
|
353
355
|
}
|
|
354
356
|
catch {
|