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 || 'https://bscfourapi.emit.tools';
123
- // 上传端点列表:主 URL + 用户自定义备用 URL + 官方 API(兜底)
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 = [...new Set([this.baseUrl, FOUR_MEME_OFFICIAL_API])];
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 = [...new Set([this.baseUrl, FOUR_MEME_OFFICIAL_API])];
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 = [...new Set([this.baseUrl, FOUR_MEME_OFFICIAL_API])];
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
- if (taxRate > 0 && taxRate !== 100) {
351
- result.taxRate = taxRate;
352
+ if (taxRate > 0 && taxRate !== 100)
353
+ result.taxRate = taxRate;
352
354
  }
353
355
  }
354
356
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "4.3.6",
3
+ "version": "4.3.7",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",