four-flap-meme-sdk 4.0.2 → 4.0.4

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.
Files changed (54) hide show
  1. package/dist/abis/openfour.d.ts +94 -0
  2. package/dist/abis/openfour.js +63 -0
  3. package/dist/bundle/four-meme/core/constants.js +1 -0
  4. package/dist/bundle/four-meme/core/create-openfour.d.ts +23 -0
  5. package/dist/bundle/four-meme/core/create-openfour.js +118 -0
  6. package/dist/bundle/four-meme/core/create-token-quote.d.ts +24 -0
  7. package/dist/bundle/four-meme/core/create-token-quote.js +53 -0
  8. package/dist/bundle/four-meme/core/create-token.js +28 -39
  9. package/dist/bundle/four-meme/core/index.d.ts +2 -0
  10. package/dist/bundle/four-meme/core/index.js +1 -0
  11. package/dist/bundle/four-meme/private/buy-quote.d.ts +35 -0
  12. package/dist/bundle/four-meme/private/buy-quote.js +70 -0
  13. package/dist/bundle/four-meme/private/buy.js +82 -87
  14. package/dist/bundle/four-meme/private/index.d.ts +2 -0
  15. package/dist/bundle/four-meme/private/index.js +1 -0
  16. package/dist/bundle/four-meme/private/openfour-buy.d.ts +10 -0
  17. package/dist/bundle/four-meme/private/openfour-buy.js +65 -0
  18. package/dist/bundle/four-meme/types/index.d.ts +13 -0
  19. package/dist/chains/bsc/constants.d.ts +1 -0
  20. package/dist/core/clients/four.d.ts +25 -17
  21. package/dist/core/clients/four.js +128 -3
  22. package/dist/core/clients/index.d.ts +1 -1
  23. package/dist/core/clients/index.js +1 -1
  24. package/dist/core/constants/addresses.d.ts +2 -0
  25. package/dist/core/constants/addresses.js +2 -0
  26. package/dist/domains/flows/create.d.ts +1 -0
  27. package/dist/domains/flows/create.js +13 -12
  28. package/dist/domains/four/contracts/tm-bundle-create.js +42 -11
  29. package/dist/domains/four/contracts/tm-bundle-helpers.d.ts +6 -0
  30. package/dist/domains/four/index.d.ts +1 -0
  31. package/dist/domains/four/index.js +1 -0
  32. package/dist/domains/four/raised-token.d.ts +38 -0
  33. package/dist/domains/four/raised-token.js +75 -0
  34. package/dist/domains/four/raised-token.test.d.ts +1 -0
  35. package/dist/domains/four/raised-token.test.js +43 -0
  36. package/dist/domains/four/tax-token.d.ts +2 -0
  37. package/dist/domains/four/tax-token.js +13 -4
  38. package/dist/domains/four/tax-token.test.d.ts +1 -0
  39. package/dist/domains/four/tax-token.test.js +89 -0
  40. package/dist/domains/openfour/create-arg.d.ts +13 -0
  41. package/dist/domains/openfour/create-arg.js +78 -0
  42. package/dist/domains/openfour/index.d.ts +4 -0
  43. package/dist/domains/openfour/index.js +3 -0
  44. package/dist/domains/openfour/registry.d.ts +4 -0
  45. package/dist/domains/openfour/registry.js +44 -0
  46. package/dist/domains/openfour/schema.d.ts +20 -0
  47. package/dist/domains/openfour/schema.js +88 -0
  48. package/dist/domains/openfour/types.d.ts +76 -0
  49. package/dist/domains/openfour/types.js +1 -0
  50. package/dist/exports/root-bundle-and-tooling.d.ts +4 -2
  51. package/dist/exports/root-bundle-and-tooling.js +2 -2
  52. package/dist/exports/root-foundations.d.ts +3 -1
  53. package/dist/exports/root-foundations.js +3 -1
  54. package/package.json +1 -1
@@ -1,4 +1,34 @@
1
1
  import { getErrorMessageFromUnknown } from '../index.js';
2
+ import { FOUR_BNB_RAISED_TOKEN_FALLBACK, parseRaisedTokens, pickRaisedToken, publishedRaisedTokens, } from '../../domains/four/raised-token.js';
3
+ export function buildCreateTokenReq(custom, raisedToken) {
4
+ const totalSupply = Number(raisedToken.totalAmount);
5
+ const raisedAmount = Number(raisedToken.totalBAmount);
6
+ const saleRate = Number(raisedToken.saleRate);
7
+ return {
8
+ name: custom.name,
9
+ shortName: custom.shortName,
10
+ desc: custom.desc,
11
+ imgUrl: custom.imgUrl,
12
+ launchTime: custom.launchTime,
13
+ label: custom.label,
14
+ webUrl: custom.webUrl,
15
+ twitterUrl: custom.twitterUrl,
16
+ telegramUrl: custom.telegramUrl,
17
+ preSale: custom.preSale,
18
+ onlyMPC: custom.onlyMPC ?? false,
19
+ ...(custom.feePlan ? { feePlan: true } : {}),
20
+ ...(custom.tokenTaxInfo ? { tokenTaxInfo: custom.tokenTaxInfo } : {}),
21
+ lpTradingFee: 0.0025,
22
+ symbol: raisedToken.symbol,
23
+ totalSupply: Number.isFinite(totalSupply) && totalSupply > 0 ? totalSupply : 1_000_000_000,
24
+ raisedAmount: Number.isFinite(raisedAmount) && raisedAmount > 0 ? raisedAmount : 18,
25
+ saleRate: Number.isFinite(saleRate) && saleRate > 0 ? saleRate : 0.8,
26
+ reserveRate: 0,
27
+ funGroup: false,
28
+ clickFun: false,
29
+ raisedToken,
30
+ };
31
+ }
2
32
  // ============================================================================
3
33
  // 图片上传缓存(按内容哈希去重,同一张图片只上传一次)
4
34
  // ✅ 使用 localStorage 持久化,刷新页面 / 重启浏览器后缓存仍然有效
@@ -239,10 +269,12 @@ export class FourClient {
239
269
  return `image-${Date.now()}.${ext}`;
240
270
  }
241
271
  async createToken(accessToken, req) {
272
+ const raisedToken = req.raisedToken ?? (await this.resolveRaisedToken(req.symbol));
273
+ const body = buildCreateTokenReq(req, raisedToken);
242
274
  const r = await fetch(`${this.baseUrl}/v1/private/token/create`, {
243
275
  method: 'POST',
244
276
  headers: { 'Content-Type': 'application/json', 'meme-web-access': accessToken },
245
- body: JSON.stringify(req),
277
+ body: JSON.stringify(body),
246
278
  });
247
279
  const j = await r.json();
248
280
  if (j.code !== '0' && j.code !== 0) {
@@ -251,8 +283,35 @@ export class FourClient {
251
283
  return j.data;
252
284
  }
253
285
  async getPublicConfig() {
254
- const r = await fetch(`${this.baseUrl}/v1/public/config`);
255
- return (await r.json());
286
+ const urls = [...new Set([this.baseUrl, FOUR_MEME_OFFICIAL_API])];
287
+ let last;
288
+ for (const url of urls) {
289
+ try {
290
+ const r = await fetch(`${url}/v1/public/config`);
291
+ if (!r.ok)
292
+ continue;
293
+ const j = (await r.json());
294
+ if (parseRaisedTokens(j).length > 0)
295
+ return j;
296
+ last = j;
297
+ }
298
+ catch {
299
+ continue;
300
+ }
301
+ }
302
+ return last ?? {};
303
+ }
304
+ async listPublishedRaisedTokens() {
305
+ return publishedRaisedTokens(parseRaisedTokens(await this.getPublicConfig()));
306
+ }
307
+ async resolveRaisedToken(symbol) {
308
+ try {
309
+ const tokens = await this.listPublishedRaisedTokens();
310
+ return pickRaisedToken(tokens, symbol) ?? pickRaisedToken(tokens, 'BNB') ?? FOUR_BNB_RAISED_TOKEN_FALLBACK;
311
+ }
312
+ catch {
313
+ return pickRaisedToken([FOUR_BNB_RAISED_TOKEN_FALLBACK], symbol) ?? FOUR_BNB_RAISED_TOKEN_FALLBACK;
314
+ }
256
315
  }
257
316
  async getTokenByAddress(address, accessToken) {
258
317
  const r = await fetch(`${this.baseUrl}/v1/private/token/get?address=${address}`, {
@@ -280,6 +339,72 @@ export class FourClient {
280
339
  }
281
340
  return (j.data ?? j);
282
341
  }
342
+ async searchTokenTemplates() {
343
+ const urls = [...new Set([this.baseUrl, FOUR_MEME_OFFICIAL_API])];
344
+ let last = [];
345
+ for (const url of urls) {
346
+ try {
347
+ const r = await fetch(`${url}/v1/public/token_template/search`, {
348
+ method: 'POST',
349
+ headers: { 'Content-Type': 'application/json' },
350
+ body: JSON.stringify({ sort: 'LAST' }),
351
+ });
352
+ if (!r.ok)
353
+ continue;
354
+ const j = (await r.json());
355
+ if (j.code !== '0' && j.code !== 0)
356
+ continue;
357
+ const list = Array.isArray(j.data) ? j.data : [];
358
+ if (list.length > 0)
359
+ return list;
360
+ last = list;
361
+ }
362
+ catch {
363
+ continue;
364
+ }
365
+ }
366
+ return last;
367
+ }
368
+ async listPublishedTokenTemplates() {
369
+ const list = await this.searchTokenTemplates();
370
+ return list.filter((t) => String(t.status || '').toUpperCase() === 'PUBLISHED');
371
+ }
372
+ async getTokenTemplateConfig(templateId) {
373
+ const urls = [...new Set([this.baseUrl, FOUR_MEME_OFFICIAL_API])];
374
+ let last = [];
375
+ for (const url of urls) {
376
+ try {
377
+ const r = await fetch(`${url}/v1/public/token_template/config?templateId=${templateId}`);
378
+ if (!r.ok)
379
+ continue;
380
+ const j = (await r.json());
381
+ if (j.code !== '0' && j.code !== 0)
382
+ continue;
383
+ const list = Array.isArray(j.data) ? j.data : [];
384
+ if (list.length > 0)
385
+ return list;
386
+ last = list;
387
+ }
388
+ catch {
389
+ continue;
390
+ }
391
+ }
392
+ return last;
393
+ }
394
+ async createOpenFourToken(accessToken, req) {
395
+ const r = await fetch(`${this.baseUrl}/v1/private/token_template/token/create`, {
396
+ method: 'POST',
397
+ headers: { 'Content-Type': 'application/json', 'meme-web-access': accessToken },
398
+ body: JSON.stringify(req),
399
+ });
400
+ const j = await r.json();
401
+ if (j.code !== '0' && j.code !== 0) {
402
+ throw new Error(`createOpenFourToken failed: ${JSON.stringify(j)}`);
403
+ }
404
+ const data = j.data;
405
+ const first = Array.isArray(data) ? data[0] : data;
406
+ return first;
407
+ }
283
408
  }
284
409
  export function buildLoginMessage(nonce) {
285
410
  return `You are sign in Meme ${nonce}`;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * HTTP / Bundle 客户端统一出口(不替代根包的扁平别名,仅供子路径导入)
3
3
  */
4
- export { FourClient, buildLoginMessage, type FourConfig, type GenerateNonceReq, type LoginReq, type CreateTokenReq, type CreateTokenResp, type FourTokenTaxInfo, } from './four.js';
4
+ export { FourClient, buildLoginMessage, buildCreateTokenReq, type FourConfig, type GenerateNonceReq, type LoginReq, type CreateTokenReq, type CreateTokenResp, type FourTokenTaxInfo, type FourRaisedToken, type FourTokenLabel, } from './four.js';
5
5
  export { Club48Client, sendBatchPrivateTransactions, sendBackrunBundle, type BundleParams, type BundleStatus, type Club48Config, } from './club48.js';
6
6
  export { MerkleClient, createMerkleClient, type MerkleConfig, type BundleParams as MerkleBundleParams, type SendBundleOptions, type BundleResult, type TransactionResult, } from './merkle.js';
7
7
  export { BlockRazorClient, createBlockRazorClient, BLOCKRAZOR_BUILDER_EOA, type BlockRazorConfig, type BlockRazorBundleParams, type SendBundleOptions as BlockRazorSendBundleOptions, type BundleResult as BlockRazorBundleResult, type TransactionResult as BlockRazorTransactionResult, type IncentiveTransactionParams, } from './blockrazor.js';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * HTTP / Bundle 客户端统一出口(不替代根包的扁平别名,仅供子路径导入)
3
3
  */
4
- export { FourClient, buildLoginMessage, } from './four.js';
4
+ export { FourClient, buildLoginMessage, buildCreateTokenReq, } from './four.js';
5
5
  export { Club48Client, sendBatchPrivateTransactions, sendBackrunBundle, } from './club48.js';
6
6
  export { MerkleClient, createMerkleClient, } from './merkle.js';
7
7
  export { BlockRazorClient, createBlockRazorClient, BLOCKRAZOR_BUILDER_EOA, } from './blockrazor.js';
@@ -22,6 +22,8 @@ export declare const ADDRESSES: {
22
22
  readonly TokenManagerV2: "0x342399a59943B5815849657Aa0e06D7058D9d5C6";
23
23
  /** 查询辅助合约(Helper3) */
24
24
  readonly TokenManagerHelper3: "0xF251F83e40a78868FcfA3FA4599Dad6494E46034";
25
+ /** OpenFour Registry(链上发现 Core / Tools) */
26
+ readonly OpenFourRegistry: "0x912CEf0C3aE9Ab6eB3Ec87cab69371cFb317Ab94";
25
27
  readonly FlapPortal: "0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0";
26
28
  readonly PancakeV2Router: "0x10ED43C718714eb63d5aA57B78B54704E256024E";
27
29
  readonly PancakeV2Factory: "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73";
@@ -34,6 +34,8 @@ export const ADDRESSES = {
34
34
  TokenManagerV2: '0x342399a59943B5815849657Aa0e06D7058D9d5C6',
35
35
  /** 查询辅助合约(Helper3) */
36
36
  TokenManagerHelper3: '0xF251F83e40a78868FcfA3FA4599Dad6494E46034',
37
+ /** OpenFour Registry(链上发现 Core / Tools) */
38
+ OpenFourRegistry: '0x912CEf0C3aE9Ab6eB3Ec87cab69371cFb317Ab94',
37
39
  // ---------- Flap Portal ----------
38
40
  FlapPortal: '0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0',
39
41
  // ---------- PancakeSwap 官方合约 ----------
@@ -19,6 +19,7 @@ export type CreateTokenFlowInput = {
19
19
  webUrl?: string;
20
20
  twitterUrl?: string;
21
21
  telegramUrl?: string;
22
+ quoteSymbol?: string;
22
23
  };
23
24
  };
24
25
  export type CreateTokenFlowOutput = {
@@ -3,6 +3,7 @@ import { createTokenOnChain } from '../four/contracts/tm.js';
3
3
  import { Wallet, parseEther } from 'ethers';
4
4
  import { readFileSync } from 'fs';
5
5
  import { basename } from 'path';
6
+ import { computeFourCreateValueWei } from '../four/raised-token.js';
6
7
  /**
7
8
  * 根据文件扩展名获取 MIME 类型
8
9
  */
@@ -59,8 +60,8 @@ export async function createTokenFlow(input) {
59
60
  imgUrl = await four.uploadImage(accessToken, imageBlob, filename);
60
61
  }
61
62
  // 4) 创建参数(REST)
63
+ const raisedToken = await four.resolveRaisedToken(input.payload.quoteSymbol);
62
64
  const createResp = await four.createToken(accessToken, {
63
- // 可自定义参数
64
65
  name: input.payload.name,
65
66
  shortName: input.payload.shortName,
66
67
  desc: input.payload.desc,
@@ -71,23 +72,23 @@ export async function createTokenFlow(input) {
71
72
  twitterUrl: input.payload.twitterUrl,
72
73
  telegramUrl: input.payload.telegramUrl,
73
74
  preSale: input.payload.preSale,
74
- onlyMPC: input.payload.onlyMPC ?? false, // 默认为 false(普通代币)
75
- // 固定参数
75
+ onlyMPC: input.payload.onlyMPC ?? false,
76
76
  lpTradingFee: 0.0025,
77
- symbol: 'BNB',
77
+ symbol: raisedToken.symbol,
78
78
  totalSupply: 1000000000,
79
- raisedAmount: 24,
80
- saleRate: 0.8,
79
+ raisedAmount: Number(raisedToken.totalBAmount) || 18,
80
+ saleRate: Number(raisedToken.saleRate) || 0.8,
81
81
  reserveRate: 0,
82
82
  funGroup: false,
83
83
  clickFun: false,
84
+ raisedToken,
85
+ });
86
+ const launchFeeWei = parseEther('0.01');
87
+ const valueWei = computeFourCreateValueWei({
88
+ raisedToken,
89
+ preSale: input.payload.preSale,
90
+ launchFeeWei,
84
91
  });
85
- // 5) 链上创建
86
- // 根据文档:b0Amount(初始金额,默认 8 BNB)+ preSale(预购金额)
87
- const b0AmountStr = input.b0Amount ?? '8'; // 用户可自定义,默认为 8 BNB
88
- const b0Amount = parseEther(b0AmountStr);
89
- const preSaleAmount = input.payload.preSale !== '0' ? parseEther(input.payload.preSale) : 0n;
90
- const valueWei = b0Amount + preSaleAmount;
91
92
  const { receipt, tokenAddress } = await createTokenOnChain({
92
93
  chain: input.networkCode,
93
94
  rpcUrl: input.rpcUrl,
@@ -8,6 +8,8 @@ import { GAS_LIMITS, getProfitRateBps, getProfitRecipient } from '../../../core/
8
8
  import { buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../../utils/bundle-helpers.js';
9
9
  import { FourClient, buildLoginMessage } from '../../../core/clients/four.js';
10
10
  import { consoleSdkLogger, getErrorMessageFromUnknown } from '../../../core/index.js';
11
+ import { ERC20_ABI } from '../../../abis/common.js';
12
+ import { computeFourCreateValueWei, isNativeRaisedToken } from '../raised-token.js';
11
13
  import { TM2_ABI, getErrorMessage, waitForBundleWithProvider, } from './tm-bundle-helpers.js';
12
14
  const sdkLogger = consoleSdkLogger;
13
15
  export async function createTokenWithBundleBuy(params) {
@@ -56,8 +58,9 @@ export async function createTokenWithBundleBuy(params) {
56
58
  throw new Error(getErrorMessage('IMAGE_REQUIRED'));
57
59
  }
58
60
  // 3. 获取创建参数
61
+ const raisedToken = await fourClient.resolveRaisedToken(tokenInfo.quoteSymbol);
62
+ const createRaised = tokenInfo.raisedToken ?? raisedToken;
59
63
  const createResp = await fourClient.createToken(accessToken, {
60
- // 可自定义参数
61
64
  name: tokenInfo.name,
62
65
  shortName: tokenInfo.symbol,
63
66
  desc: tokenInfo.description,
@@ -69,15 +72,15 @@ export async function createTokenWithBundleBuy(params) {
69
72
  telegramUrl: tokenInfo.telegramUrl,
70
73
  preSale: tokenInfo.preSale || '0',
71
74
  onlyMPC: false,
72
- // 固定参数
73
75
  lpTradingFee: 0.0025,
74
- symbol: 'BNB',
76
+ symbol: createRaised.symbol,
75
77
  totalSupply: 1000000000,
76
- raisedAmount: 24,
77
- saleRate: 0.8,
78
+ raisedAmount: Number(createRaised.totalBAmount) || 18,
79
+ saleRate: Number(createRaised.saleRate) || 0.8,
78
80
  reserveRate: 0,
79
81
  funGroup: false,
80
82
  clickFun: false,
83
+ raisedToken: createRaised,
81
84
  });
82
85
  // 4. 构建交易
83
86
  const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
@@ -107,13 +110,41 @@ export async function createTokenWithBundleBuy(params) {
107
110
  };
108
111
  // four.meme 只支持 BNB,固定使用 0 地址
109
112
  const tokenAddress = ZERO_ADDRESS;
110
- // 4.1 创建交易
111
- // 根据文档:b0Amount(初始金额,默认 8 BNB)+ preSale(预购金额)
112
- const b0AmountStr = params.b0Amount ?? '8';
113
- const b0AmountWei = ethers.parseEther(b0AmountStr);
114
- const preSaleWei = tokenInfo.preSale ? ethers.parseEther(tokenInfo.preSale) : 0n;
115
- const valueWei = b0AmountWei + preSaleWei;
116
113
  const tm2 = new ethers.Contract(tmAddr, TM2_ABI, devWallet);
114
+ let launchFeeWei = ethers.parseEther('0.01');
115
+ try {
116
+ const fee = await tm2._launchFee();
117
+ if (typeof fee === 'bigint' && fee > 0n)
118
+ launchFeeWei = fee;
119
+ }
120
+ catch {
121
+ // TokenManager2 JSON ABI 可能没有 _launchFee
122
+ }
123
+ const valueWei = computeFourCreateValueWei({
124
+ raisedToken: createRaised,
125
+ preSale: tokenInfo.preSale || '0',
126
+ launchFeeWei,
127
+ });
128
+ const preSaleStr = tokenInfo.preSale || '0';
129
+ if (!isNativeRaisedToken(createRaised) && preSaleStr !== '0') {
130
+ const quote = new ethers.Contract(createRaised.symbolAddress, ERC20_ABI, devWallet);
131
+ const decimals = Number(await quote.decimals());
132
+ const amount = ethers.parseUnits(preSaleStr, decimals);
133
+ const allowance = await quote.allowance(devWallet.address, tmAddr);
134
+ if (allowance < amount) {
135
+ const approveUnsigned = await quote.approve.populateTransaction(tmAddr, amount);
136
+ const signedApprove = await devWallet.signTransaction({
137
+ ...approveUnsigned,
138
+ from: devWallet.address,
139
+ nonce: await getNextNonce(devWallet),
140
+ gasLimit: 80000n,
141
+ gasPrice,
142
+ chainId: CHAINS.BSC.chainId,
143
+ type: 0,
144
+ });
145
+ signedTxs.push(signedApprove);
146
+ }
147
+ }
117
148
  const createTxUnsigned = await tm2.createToken.populateTransaction(createResp.createArg, createResp.signature, {
118
149
  value: valueWei,
119
150
  });
@@ -2,6 +2,8 @@ import { JsonRpcProvider } from 'ethers';
2
2
  import type { InterfaceAbi, TransactionRequest } from 'ethers';
3
3
  import type { SoulPointSignatureMode, VNormalizationMode } from '../../../core/clients/club48.js';
4
4
  import { BundleStatus } from '../../../core/clients/club48.js';
5
+ import type { FourTokenTaxInfo } from '../../../core/clients/four.js';
6
+ import type { FourRaisedToken } from '../raised-token.js';
5
7
  import type { GeneratedWallet } from '../../../utils/wallet.js';
6
8
  export declare const TM2_ABI: InterfaceAbi;
7
9
  /**
@@ -67,6 +69,10 @@ export type FourCreateWithBundleBuyParams = {
67
69
  webUrl?: string;
68
70
  twitterUrl?: string;
69
71
  telegramUrl?: string;
72
+ tokenTaxInfo?: FourTokenTaxInfo;
73
+ feePlan?: boolean;
74
+ quoteSymbol?: string;
75
+ raisedToken?: FourRaisedToken;
70
76
  };
71
77
  config: FourBundleConfig;
72
78
  };
@@ -1,4 +1,5 @@
1
1
  /** four.meme domain facade */
2
+ export * from './raised-token.js';
2
3
  export * from './tax-token.js';
3
4
  export * from './contracts/tm.js';
4
5
  export * from './contracts/tm1.js';
@@ -1,4 +1,5 @@
1
1
  /** four.meme domain facade */
2
+ export * from './raised-token.js';
2
3
  export * from './tax-token.js';
3
4
  export * from './contracts/tm.js';
4
5
  export * from './contracts/tm1.js';
@@ -0,0 +1,38 @@
1
+ export declare const FOUR_TOKEN_LABELS: readonly ["Meme", "AI", "Defi", "Games", "Infra", "De-Sci", "Social", "Depin", "Charity", "Others"];
2
+ export type FourTokenLabel = (typeof FOUR_TOKEN_LABELS)[number];
3
+ export type FourRaisedToken = {
4
+ symbol: string;
5
+ nativeSymbol: string;
6
+ symbolAddress: string;
7
+ deployCost: string;
8
+ buyFee: string;
9
+ sellFee: string;
10
+ minTradeFee: string;
11
+ b0Amount: string;
12
+ totalBAmount: string;
13
+ totalAmount: string;
14
+ logoUrl: string;
15
+ tradeLevel: string[];
16
+ status: string;
17
+ buyTokenLink: string;
18
+ reservedNumber: number;
19
+ saleRate: string;
20
+ networkCode: string;
21
+ platform: string;
22
+ isRwa?: boolean;
23
+ };
24
+ /** 官网当前 PUBLISH 的 BNB 档,仅在拉配置失败时兜底(字段与 /public/config 一致) */
25
+ export declare const FOUR_BNB_RAISED_TOKEN_FALLBACK: FourRaisedToken;
26
+ export declare function parseRaisedTokens(raw: unknown): FourRaisedToken[];
27
+ export declare function publishedRaisedTokens(tokens: FourRaisedToken[]): FourRaisedToken[];
28
+ export declare function pickRaisedToken(tokens: FourRaisedToken[], symbol?: string): FourRaisedToken | undefined;
29
+ export declare function isNativeRaisedToken(token: FourRaisedToken): boolean;
30
+ /** buyFee 如 "0.01" 表示 1%,换算成 TokenManager 的 万分比 */
31
+ export declare function raisedTokenTradingFeeBps(token: FourRaisedToken): bigint;
32
+ /** TokenManager2.createToken 的 msg.value:ERC20 计价只付 launch fee;BNB 预购再加 preSale + 交易费 */
33
+ export declare function computeFourCreateValueWei(params: {
34
+ raisedToken: FourRaisedToken;
35
+ preSale: string;
36
+ launchFeeWei: bigint;
37
+ tradingFeeRateBps?: bigint;
38
+ }): bigint;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Four.meme 发币计价币(raisedToken)
3
+ * 来源:GET /v1/public/config,官网 create-token 只展示 status=PUBLISH
4
+ */
5
+ import { parseEther } from 'ethers';
6
+ export const FOUR_TOKEN_LABELS = [
7
+ 'Meme',
8
+ 'AI',
9
+ 'Defi',
10
+ 'Games',
11
+ 'Infra',
12
+ 'De-Sci',
13
+ 'Social',
14
+ 'Depin',
15
+ 'Charity',
16
+ 'Others',
17
+ ];
18
+ /** 官网当前 PUBLISH 的 BNB 档,仅在拉配置失败时兜底(字段与 /public/config 一致) */
19
+ export const FOUR_BNB_RAISED_TOKEN_FALLBACK = {
20
+ symbol: 'BNB',
21
+ nativeSymbol: 'BNB',
22
+ symbolAddress: '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c',
23
+ deployCost: '0',
24
+ buyFee: '0.01',
25
+ sellFee: '0.01',
26
+ minTradeFee: '0',
27
+ b0Amount: '8',
28
+ totalBAmount: '18',
29
+ totalAmount: '1000000000',
30
+ logoUrl: '',
31
+ tradeLevel: ['0.1', '0.5', '1'],
32
+ status: 'PUBLISH',
33
+ buyTokenLink: 'https://pancakeswap.finance/swap',
34
+ reservedNumber: 10,
35
+ saleRate: '0.8',
36
+ networkCode: 'BSC',
37
+ platform: 'MEME',
38
+ isRwa: false,
39
+ };
40
+ export function parseRaisedTokens(raw) {
41
+ if (!raw || typeof raw !== 'object')
42
+ return [];
43
+ const data = 'data' in raw ? raw.data : raw;
44
+ if (!Array.isArray(data))
45
+ return [];
46
+ return data.filter((item) => !!item && typeof item === 'object' && typeof item.symbol === 'string');
47
+ }
48
+ export function publishedRaisedTokens(tokens) {
49
+ return tokens.filter((t) => String(t.status || '').toUpperCase() === 'PUBLISH');
50
+ }
51
+ export function pickRaisedToken(tokens, symbol) {
52
+ const key = String(symbol || 'BNB').toUpperCase();
53
+ return tokens.find((t) => t.symbol.toUpperCase() === key);
54
+ }
55
+ export function isNativeRaisedToken(token) {
56
+ return String(token.symbol || '').toUpperCase() === 'BNB';
57
+ }
58
+ /** buyFee 如 "0.01" 表示 1%,换算成 TokenManager 的 万分比 */
59
+ export function raisedTokenTradingFeeBps(token) {
60
+ const fee = Number(token.buyFee);
61
+ if (!Number.isFinite(fee) || fee <= 0)
62
+ return 100n;
63
+ return BigInt(Math.round(fee * 10000));
64
+ }
65
+ /** TokenManager2.createToken 的 msg.value:ERC20 计价只付 launch fee;BNB 预购再加 preSale + 交易费 */
66
+ export function computeFourCreateValueWei(params) {
67
+ const preSale = String(params.preSale || '').trim() || '0';
68
+ if (!isNativeRaisedToken(params.raisedToken) || preSale === '0') {
69
+ return params.launchFeeWei;
70
+ }
71
+ const preSaleWei = parseEther(preSale);
72
+ const rate = params.tradingFeeRateBps ?? raisedTokenTradingFeeBps(params.raisedToken);
73
+ const tradingFee = (preSaleWei * rate) / 10000n;
74
+ return params.launchFeeWei + preSaleWei + tradingFee;
75
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseEther } from 'ethers';
3
+ import { FOUR_BNB_RAISED_TOKEN_FALLBACK, computeFourCreateValueWei, isNativeRaisedToken, parseRaisedTokens, pickRaisedToken, publishedRaisedTokens, raisedTokenTradingFeeBps, } from './raised-token.js';
4
+ const usdt = {
5
+ ...FOUR_BNB_RAISED_TOKEN_FALLBACK,
6
+ symbol: 'USDT',
7
+ nativeSymbol: 'USDT',
8
+ symbolAddress: '0x55d398326f99059ff775485246999027b3197955',
9
+ b0Amount: '4000',
10
+ totalBAmount: '12000',
11
+ buyFee: '0.01',
12
+ };
13
+ describe('four raised-token', () => {
14
+ it('parses public config array and keeps PUBLISH only', () => {
15
+ const tokens = parseRaisedTokens({
16
+ code: 0,
17
+ data: [FOUR_BNB_RAISED_TOKEN_FALLBACK, { ...usdt, status: 'INIT' }, usdt],
18
+ });
19
+ expect(tokens.map((t) => t.symbol)).toEqual(['BNB', 'USDT', 'USDT']);
20
+ expect(publishedRaisedTokens(tokens).map((t) => t.symbol)).toEqual(['BNB', 'USDT']);
21
+ expect(pickRaisedToken(tokens, 'usdt')?.symbol).toBe('USDT');
22
+ });
23
+ it('BNB presale value is launch fee + presale + trading fee, not b0Amount', () => {
24
+ const launch = parseEther('0.01');
25
+ const preSale = '1';
26
+ const value = computeFourCreateValueWei({
27
+ raisedToken: FOUR_BNB_RAISED_TOKEN_FALLBACK,
28
+ preSale,
29
+ launchFeeWei: launch,
30
+ });
31
+ const trading = (parseEther('1') * raisedTokenTradingFeeBps(FOUR_BNB_RAISED_TOKEN_FALLBACK)) / 10000n;
32
+ expect(value).toBe(launch + parseEther('1') + trading);
33
+ expect(isNativeRaisedToken(FOUR_BNB_RAISED_TOKEN_FALLBACK)).toBe(true);
34
+ });
35
+ it('ERC20 quote create value is launch fee only', () => {
36
+ expect(isNativeRaisedToken(usdt)).toBe(false);
37
+ expect(computeFourCreateValueWei({
38
+ raisedToken: usdt,
39
+ preSale: '100',
40
+ launchFeeWei: parseEther('0.01'),
41
+ })).toBe(parseEther('0.01'));
42
+ });
43
+ });
@@ -169,6 +169,8 @@ export declare function validateFourTokenTaxInfo(info: {
169
169
  liquidityRate: number;
170
170
  recipientRate: number;
171
171
  minSharing: number;
172
+ giggleCharityRate?: number;
173
+ binanceCharityRate?: number;
172
174
  }): {
173
175
  valid: boolean;
174
176
  error?: string;
@@ -296,16 +296,25 @@ export function validateFourTokenTaxInfo(info) {
296
296
  if (![1, 3, 5, 10].includes(info.feeRate)) {
297
297
  return { valid: false, error: `feeRate 必须为 1, 3, 5 或 10,当前: ${info.feeRate}` };
298
298
  }
299
- // 四项比例之和必须为 100
300
- const rateSum = info.burnRate + info.divideRate + info.liquidityRate + info.recipientRate;
299
+ const giggle = info.giggleCharityRate ?? 0;
300
+ const binance = info.binanceCharityRate ?? 0;
301
+ const isTaxToken9 = info.giggleCharityRate != null || info.binanceCharityRate != null;
302
+ const rateSum = info.burnRate + info.divideRate + info.liquidityRate + info.recipientRate + giggle + binance;
301
303
  if (rateSum !== 100) {
302
304
  return {
303
305
  valid: false,
304
- error: `burnRate + divideRate + liquidityRate + recipientRate 必须等于 100,当前: ${rateSum}`,
306
+ error: isTaxToken9
307
+ ? `burnRate + divideRate + liquidityRate + recipientRate + giggleCharityRate + binanceCharityRate 必须等于 100,当前: ${rateSum}`
308
+ : `burnRate + divideRate + liquidityRate + recipientRate 必须等于 100,当前: ${rateSum}`,
305
309
  };
306
310
  }
307
311
  // 每项比例 >= 0
308
- if (info.burnRate < 0 || info.divideRate < 0 || info.liquidityRate < 0 || info.recipientRate < 0) {
312
+ if (info.burnRate < 0 ||
313
+ info.divideRate < 0 ||
314
+ info.liquidityRate < 0 ||
315
+ info.recipientRate < 0 ||
316
+ giggle < 0 ||
317
+ binance < 0) {
309
318
  return { valid: false, error: '所有比例值不能为负数' };
310
319
  }
311
320
  // minSharing 验证: d × 10^n, n >= 5, 1 <= d <= 9
@@ -0,0 +1 @@
1
+ export {};