@four-meme/four-meme-ai 1.0.4 → 1.0.6
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/CLAUDE.md +91 -91
- package/README.md +136 -108
- package/bin/fourmeme.cjs +171 -128
- package/package.json +2 -1
- package/skills/four-meme-integration/SKILL.md +18 -25
- package/skills/four-meme-integration/references/agent-creator-and-wallets.md +87 -0
- package/skills/four-meme-integration/references/api-create-token.md +55 -55
- package/skills/four-meme-integration/references/contract-addresses.md +47 -34
- package/skills/four-meme-integration/references/create-token-scripts.md +111 -63
- package/skills/four-meme-integration/references/errors.md +29 -27
- package/skills/four-meme-integration/references/event-listening.md +75 -75
- package/skills/four-meme-integration/references/execute-trade.md +31 -31
- package/skills/four-meme-integration/references/tax-token-query.md +38 -38
- package/skills/four-meme-integration/references/token-tax-info.md +77 -77
- package/skills/four-meme-integration/scripts/8004-balance.ts +52 -52
- package/skills/four-meme-integration/scripts/8004-register.ts +108 -108
- package/skills/four-meme-integration/scripts/create-token-api.ts +321 -251
- package/skills/four-meme-integration/scripts/create-token-chain.ts +102 -85
- package/skills/four-meme-integration/scripts/create-token-instant.ts +106 -0
- package/skills/four-meme-integration/scripts/execute-buy.ts +198 -198
- package/skills/four-meme-integration/scripts/execute-sell.ts +150 -150
- package/skills/four-meme-integration/scripts/send-token.ts +98 -98
|
@@ -1,251 +1,321 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Four.meme - create token API flow (nonce → login → upload image → create).
|
|
4
|
-
* Outputs createArg and signature (hex) for use with create-token-chain.ts.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* npx tsx create-token-api.ts
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Optional
|
|
11
|
-
* Tax token:
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
20
|
-
import { basename } from 'node:path';
|
|
21
|
-
|
|
22
|
-
const API_BASE = 'https://four.meme/meme-api/v1';
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Four.meme - create token API flow (nonce → login → upload image → create).
|
|
4
|
+
* Outputs createArg and signature (hex) for use with create-token-chain.ts.
|
|
5
|
+
*
|
|
6
|
+
* Usage: all options as --key=value
|
|
7
|
+
* npx tsx create-token-api.ts --image=./logo.png --name=MyToken --short-name=MTK --desc="My desc" --label=AI [options]
|
|
8
|
+
*
|
|
9
|
+
* Required: --image= --name= --short-name= --desc= --label=
|
|
10
|
+
* Optional: --web-url= --twitter-url= --telegram-url= (omit if empty); --pre-sale=0 (in BNB/ether, e.g. 0.001); --fee-plan=false --tax-options=<path>
|
|
11
|
+
* Tax token: --tax-options=tax.json or --tax-token --tax-fee-rate=5 ... (burn+divide+liquidity+recipient=100)
|
|
12
|
+
* Labels: Meme | AI | Defi | Games | Infra | De-Sci | Social | Depin | Charity | Others
|
|
13
|
+
* Env: PRIVATE_KEY
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createPublicClient, http, parseAbi } from 'viem';
|
|
17
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
18
|
+
import { bsc } from 'viem/chains';
|
|
19
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
20
|
+
import { basename } from 'node:path';
|
|
21
|
+
|
|
22
|
+
const API_BASE = 'https://four.meme/meme-api/v1';
|
|
23
|
+
const TOKEN_MANAGER2_BSC = '0x5c952063c7fc8610FFDB798152D69F0B9550762b' as const;
|
|
24
|
+
const TM2_ABI = parseAbi([
|
|
25
|
+
'function _launchFee() view returns (uint256)',
|
|
26
|
+
'function _tradingFeeRate() view returns (uint256)',
|
|
27
|
+
]);
|
|
28
|
+
const NETWORK_CODE = 'BSC';
|
|
29
|
+
|
|
30
|
+
/** Get option from argv: --key=value or --key value; fallback to env (key as UPPER_SNAKE). */
|
|
31
|
+
function getOpt(key: string, defaultValue: string): string {
|
|
32
|
+
const prefix = key + '=';
|
|
33
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
34
|
+
const arg = process.argv[i];
|
|
35
|
+
if (arg === key && i + 1 < process.argv.length) return process.argv[i + 1];
|
|
36
|
+
if (arg.startsWith(prefix)) return arg.slice(prefix.length);
|
|
37
|
+
}
|
|
38
|
+
return process.env[key.replace(/-/g, '_').toUpperCase()] ?? defaultValue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Get boolean option from argv: --fee-plan or --fee-plan=true; fallback to env. */
|
|
42
|
+
function getOptBool(key: string, defaultValue: boolean): boolean {
|
|
43
|
+
const prefix = key + '=';
|
|
44
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
45
|
+
const arg = process.argv[i];
|
|
46
|
+
if (arg === key) return true;
|
|
47
|
+
if (arg.startsWith(prefix)) {
|
|
48
|
+
const v = arg.slice(prefix.length).toLowerCase();
|
|
49
|
+
return v === '1' || v === 'true' || v === 'yes';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const envKey = key.replace(/-/g, '_').toUpperCase();
|
|
53
|
+
if (process.env[envKey] !== undefined) {
|
|
54
|
+
const v = process.env[envKey]!.toLowerCase();
|
|
55
|
+
return v === '1' || v === 'true' || v === 'yes';
|
|
56
|
+
}
|
|
57
|
+
return defaultValue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function toHex(value: string): string {
|
|
61
|
+
if (value.startsWith('0x')) return value;
|
|
62
|
+
if (/^[0-9a-fA-F]+$/.test(value)) return '0x' + value;
|
|
63
|
+
const buf = Buffer.from(value, 'base64');
|
|
64
|
+
return '0x' + buf.toString('hex');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function main() {
|
|
68
|
+
const privateKey = process.env.PRIVATE_KEY;
|
|
69
|
+
if (!privateKey) {
|
|
70
|
+
console.error('Set PRIVATE_KEY');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const pk = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`);
|
|
74
|
+
const account = privateKeyToAccount(pk);
|
|
75
|
+
const address = account.address;
|
|
76
|
+
|
|
77
|
+
const imagePath = getOpt('--image', '');
|
|
78
|
+
const name = getOpt('--name', '');
|
|
79
|
+
const shortName = getOpt('--short-name', '');
|
|
80
|
+
const desc = getOpt('--desc', '');
|
|
81
|
+
const label = getOpt('--label', '');
|
|
82
|
+
const taxOptionsPath = getOpt('--tax-options', '');
|
|
83
|
+
|
|
84
|
+
if (!imagePath || !name || !shortName || !desc || !label) {
|
|
85
|
+
console.error(
|
|
86
|
+
'Usage: npx tsx create-token-api.ts --image=<path> --name= --short-name= --desc= --label= [options]'
|
|
87
|
+
);
|
|
88
|
+
console.error('Example: npx tsx create-token-api.ts --image=./logo.png --name=MyToken --short-name=MTK --desc="My desc" --label=AI');
|
|
89
|
+
console.error('Required: --image= --name= --short-name= --desc= --label=');
|
|
90
|
+
console.error('Optional: --web-url= --twitter-url= --telegram-url= --pre-sale=0 --fee-plan=false --tax-options=<path>');
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
if (!existsSync(imagePath)) {
|
|
94
|
+
console.error('Image file not found:', imagePath);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const validLabels = ['Meme', 'AI', 'Defi', 'Games', 'Infra', 'De-Sci', 'Social', 'Depin', 'Charity', 'Others'];
|
|
99
|
+
const labelNorm = validLabels.find((l) => l.toLowerCase() === label.toLowerCase());
|
|
100
|
+
if (!labelNorm) {
|
|
101
|
+
console.error('Invalid label. Use one of:', validLabels.join(', '));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
const labelCanonical = labelNorm; // API expects exact label from list (case-sensitive)
|
|
105
|
+
|
|
106
|
+
// 1. Get nonce
|
|
107
|
+
const nonceRes = await fetch(`${API_BASE}/private/user/nonce/generate`, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: { 'Content-Type': 'application/json' },
|
|
110
|
+
body: JSON.stringify({
|
|
111
|
+
accountAddress: address,
|
|
112
|
+
verifyType: 'LOGIN',
|
|
113
|
+
networkCode: NETWORK_CODE,
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
const nonceData = await nonceRes.json();
|
|
117
|
+
if (nonceData.code !== '0' && nonceData.code !== 0) {
|
|
118
|
+
throw new Error('Nonce failed: ' + JSON.stringify(nonceData));
|
|
119
|
+
}
|
|
120
|
+
const nonce = nonceData.data;
|
|
121
|
+
|
|
122
|
+
// 2. Sign and login
|
|
123
|
+
const message = `You are sign in Meme ${nonce}`;
|
|
124
|
+
const signature = await account.signMessage({ message });
|
|
125
|
+
|
|
126
|
+
const loginRes = await fetch(`${API_BASE}/private/user/login/dex`, {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
headers: { 'Content-Type': 'application/json' },
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
region: 'WEB',
|
|
131
|
+
langType: 'EN',
|
|
132
|
+
loginIp: '',
|
|
133
|
+
inviteCode: '',
|
|
134
|
+
verifyInfo: {
|
|
135
|
+
address,
|
|
136
|
+
networkCode: NETWORK_CODE,
|
|
137
|
+
signature,
|
|
138
|
+
verifyType: 'LOGIN',
|
|
139
|
+
},
|
|
140
|
+
walletName: 'MetaMask',
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
const loginData = await loginRes.json();
|
|
144
|
+
if (loginData.code !== '0' && loginData.code !== 0) {
|
|
145
|
+
throw new Error('Login failed: ' + JSON.stringify(loginData));
|
|
146
|
+
}
|
|
147
|
+
const accessToken = loginData.data;
|
|
148
|
+
|
|
149
|
+
// 3. Upload image
|
|
150
|
+
const imageBuffer = readFileSync(imagePath);
|
|
151
|
+
const form = new FormData();
|
|
152
|
+
form.append('file', new Blob([imageBuffer]), basename(imagePath));
|
|
153
|
+
|
|
154
|
+
const uploadRes = await fetch(`${API_BASE}/private/token/upload`, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: { 'meme-web-access': accessToken },
|
|
157
|
+
body: form as unknown as BodyInit,
|
|
158
|
+
});
|
|
159
|
+
const uploadData = await uploadRes.json();
|
|
160
|
+
if (uploadData.code !== '0' && uploadData.code !== 0) {
|
|
161
|
+
throw new Error('Upload failed: ' + JSON.stringify(uploadData));
|
|
162
|
+
}
|
|
163
|
+
const imgUrl = uploadData.data;
|
|
164
|
+
|
|
165
|
+
// 4. Public config for raisedToken (data[]: symbol, symbolAddress, totalBAmount, status=PUBLISH|INIT, ...)
|
|
166
|
+
const configRes = await fetch(`${API_BASE}/public/config`);
|
|
167
|
+
if (!configRes.ok) {
|
|
168
|
+
throw new Error('Public config request failed: ' + configRes.status + ' ' + configRes.statusText);
|
|
169
|
+
}
|
|
170
|
+
const configData = await configRes.json();
|
|
171
|
+
if (configData.code !== '0' && configData.code !== 0) {
|
|
172
|
+
throw new Error('Invalid public config response: ' + JSON.stringify(configData));
|
|
173
|
+
}
|
|
174
|
+
const symbols = configData.data;
|
|
175
|
+
if (!Array.isArray(symbols) || symbols.length === 0) {
|
|
176
|
+
throw new Error('Invalid public config (no raisedToken): ' + JSON.stringify(configData));
|
|
177
|
+
}
|
|
178
|
+
// Prefer BNB with status PUBLISH for BSC; else first PUBLISH; else first item
|
|
179
|
+
const published = symbols.filter((c: { status?: string }) => c.status === 'PUBLISH');
|
|
180
|
+
const list = published.length > 0 ? published : symbols;
|
|
181
|
+
const config =
|
|
182
|
+
list.find((c: { symbol?: string }) => c.symbol === 'BNB') ?? list[0];
|
|
183
|
+
const raisedToken = config;
|
|
184
|
+
if (!raisedToken || !raisedToken.symbol) {
|
|
185
|
+
throw new Error('Invalid public config (no raisedToken): ' + JSON.stringify(configData));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 5. Build create body and optional tokenTaxInfo
|
|
189
|
+
// raisedAmount / totalSupply / saleRate from raisedToken or docs (API-CreateToken)
|
|
190
|
+
const launchTime = Date.now();
|
|
191
|
+
const totalSupply =
|
|
192
|
+
typeof (raisedToken as { totalAmount?: string | number }).totalAmount !== 'undefined'
|
|
193
|
+
? Number((raisedToken as { totalAmount?: string | number }).totalAmount)
|
|
194
|
+
: 1000000000;
|
|
195
|
+
const raisedAmount =
|
|
196
|
+
typeof (raisedToken as { totalBAmount?: string | number }).totalBAmount !== 'undefined'
|
|
197
|
+
? Number((raisedToken as { totalBAmount?: string | number }).totalBAmount)
|
|
198
|
+
: 24;
|
|
199
|
+
const body: Record<string, unknown> = {
|
|
200
|
+
name,
|
|
201
|
+
shortName,
|
|
202
|
+
desc,
|
|
203
|
+
totalSupply,
|
|
204
|
+
raisedAmount,
|
|
205
|
+
saleRate:
|
|
206
|
+
typeof (raisedToken as { saleRate?: string | number }).saleRate !== 'undefined'
|
|
207
|
+
? Number((raisedToken as { saleRate?: string | number }).saleRate)
|
|
208
|
+
: 0.8,
|
|
209
|
+
reserveRate: 0,
|
|
210
|
+
imgUrl,
|
|
211
|
+
raisedToken,
|
|
212
|
+
launchTime,
|
|
213
|
+
funGroup: false,
|
|
214
|
+
label: labelCanonical,
|
|
215
|
+
lpTradingFee: 0.0025,
|
|
216
|
+
preSale: getOpt('--pre-sale', '0'),
|
|
217
|
+
clickFun: false,
|
|
218
|
+
symbol: (raisedToken as { symbol: string }).symbol,
|
|
219
|
+
dexType: 'PANCAKE_SWAP',
|
|
220
|
+
rushMode: false,
|
|
221
|
+
onlyMPC: false,
|
|
222
|
+
feePlan: getOptBool('--fee-plan', false),
|
|
223
|
+
};
|
|
224
|
+
const webUrl = getOpt('--web-url', '');
|
|
225
|
+
const twitterUrl = getOpt('--twitter-url', '');
|
|
226
|
+
const telegramUrl = getOpt('--telegram-url', '');
|
|
227
|
+
if (webUrl != null && webUrl !== '') body.webUrl = webUrl;
|
|
228
|
+
if (twitterUrl != null && twitterUrl !== '') body.twitterUrl = twitterUrl;
|
|
229
|
+
if (telegramUrl != null && telegramUrl !== '') body.telegramUrl = telegramUrl;
|
|
230
|
+
|
|
231
|
+
let tokenTaxInfo: Record<string, unknown> | null = null;
|
|
232
|
+
if (taxOptionsPath && existsSync(taxOptionsPath)) {
|
|
233
|
+
const taxOpts = JSON.parse(readFileSync(taxOptionsPath, 'utf8'));
|
|
234
|
+
if (taxOpts.tokenTaxInfo && typeof taxOpts.tokenTaxInfo === 'object') {
|
|
235
|
+
tokenTaxInfo = taxOpts.tokenTaxInfo as Record<string, unknown>;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const taxFromCli =
|
|
239
|
+
getOptBool('--tax-token', false) ||
|
|
240
|
+
getOpt('--tax-fee-rate', '') !== '' ||
|
|
241
|
+
process.env.TAX_TOKEN === '1';
|
|
242
|
+
if (!tokenTaxInfo && taxFromCli) {
|
|
243
|
+
const feeRate = Number(getOpt('--tax-fee-rate', '5'));
|
|
244
|
+
const burnRate = Number(getOpt('--tax-burn-rate', '0'));
|
|
245
|
+
const divideRate = Number(getOpt('--tax-divide-rate', '0'));
|
|
246
|
+
const liquidityRate = Number(getOpt('--tax-liquidity-rate', '100'));
|
|
247
|
+
const recipientRate = Number(getOpt('--tax-recipient-rate', '0'));
|
|
248
|
+
const recipientAddress = getOpt('--tax-recipient-address', '');
|
|
249
|
+
const minSharing = Number(getOpt('--tax-min-sharing', '100000'));
|
|
250
|
+
const sum = burnRate + divideRate + liquidityRate + recipientRate;
|
|
251
|
+
if (sum !== 100) {
|
|
252
|
+
throw new Error(`Tax rates must sum to 100 (burn+divide+liquidity+recipient). Got ${sum}.`);
|
|
253
|
+
}
|
|
254
|
+
if (![1, 3, 5, 10].includes(feeRate)) {
|
|
255
|
+
throw new Error('TAX_FEE_RATE must be 1, 3, 5, or 10.');
|
|
256
|
+
}
|
|
257
|
+
tokenTaxInfo = {
|
|
258
|
+
feeRate,
|
|
259
|
+
burnRate,
|
|
260
|
+
divideRate,
|
|
261
|
+
liquidityRate,
|
|
262
|
+
recipientRate,
|
|
263
|
+
recipientAddress,
|
|
264
|
+
minSharing,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (tokenTaxInfo) {
|
|
268
|
+
body.tokenTaxInfo = tokenTaxInfo;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const createRes = await fetch(`${API_BASE}/private/token/create`, {
|
|
272
|
+
method: 'POST',
|
|
273
|
+
headers: {
|
|
274
|
+
'meme-web-access': accessToken,
|
|
275
|
+
'Content-Type': 'application/json',
|
|
276
|
+
},
|
|
277
|
+
body: JSON.stringify(body),
|
|
278
|
+
});
|
|
279
|
+
const createData = await createRes.json();
|
|
280
|
+
if (createData.code !== '0' && createData.code !== 0) {
|
|
281
|
+
throw new Error('Create API failed: ' + JSON.stringify(createData));
|
|
282
|
+
}
|
|
283
|
+
const { createArg: rawArg, signature: rawSig } = createData.data;
|
|
284
|
+
const createArgHex = toHex(rawArg);
|
|
285
|
+
const signatureHex = toHex(rawSig);
|
|
286
|
+
|
|
287
|
+
// Estimate required value (CREATION_FEE_WEI) for createToken tx
|
|
288
|
+
const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
|
|
289
|
+
const client = createPublicClient({ chain: bsc, transport: http(rpcUrl) });
|
|
290
|
+
const launchFee = await client.readContract({
|
|
291
|
+
address: TOKEN_MANAGER2_BSC,
|
|
292
|
+
abi: TM2_ABI,
|
|
293
|
+
functionName: '_launchFee',
|
|
294
|
+
});
|
|
295
|
+
const preSaleStr = String(body.preSale ?? '0');
|
|
296
|
+
// API preSale is in ether (BNB); convert to wei for value calculation
|
|
297
|
+
const presaleWei = BigInt(Math.round(parseFloat(preSaleStr || '0') * 1e18));
|
|
298
|
+
const quoteIsBnb = (raisedToken as { symbol?: string }).symbol === 'BNB';
|
|
299
|
+
let requiredValueWei = launchFee;
|
|
300
|
+
if (presaleWei > 0n && quoteIsBnb) {
|
|
301
|
+
const feeRate = await client.readContract({
|
|
302
|
+
address: TOKEN_MANAGER2_BSC,
|
|
303
|
+
abi: TM2_ABI,
|
|
304
|
+
functionName: '_tradingFeeRate',
|
|
305
|
+
});
|
|
306
|
+
const tradingFee = (presaleWei * feeRate) / 10000n;
|
|
307
|
+
requiredValueWei = launchFee + presaleWei + tradingFee;
|
|
308
|
+
}
|
|
309
|
+
const creationFeeWei = requiredValueWei.toString();
|
|
310
|
+
|
|
311
|
+
const out = { createArg: createArgHex, signature: signatureHex, creationFeeWei };
|
|
312
|
+
console.log(JSON.stringify(out, null, 2));
|
|
313
|
+
console.error(
|
|
314
|
+
`\n→ For create-token-chain pass --value=${creationFeeWei} (or more).`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
main().catch((e) => {
|
|
319
|
+
console.error(e.message || e);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
});
|