four-flap-meme-sdk 4.3.6 → 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.
|
@@ -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 {
|
|
@@ -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 {
|