fourmm 0.1.0

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 (151) hide show
  1. package/README.md +147 -0
  2. package/dist/bin.d.ts +9 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +14 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/cli.d.ts +319 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +25 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/commands/config.d.ts +35 -0
  11. package/dist/commands/config.d.ts.map +1 -0
  12. package/dist/commands/config.js +145 -0
  13. package/dist/commands/config.js.map +1 -0
  14. package/dist/commands/query.d.ts +51 -0
  15. package/dist/commands/query.d.ts.map +1 -0
  16. package/dist/commands/query.js +364 -0
  17. package/dist/commands/query.js.map +1 -0
  18. package/dist/commands/token.d.ts +55 -0
  19. package/dist/commands/token.d.ts.map +1 -0
  20. package/dist/commands/token.js +650 -0
  21. package/dist/commands/token.js.map +1 -0
  22. package/dist/commands/tools.d.ts +54 -0
  23. package/dist/commands/tools.d.ts.map +1 -0
  24. package/dist/commands/tools.js +499 -0
  25. package/dist/commands/tools.js.map +1 -0
  26. package/dist/commands/trade.d.ts +63 -0
  27. package/dist/commands/trade.d.ts.map +1 -0
  28. package/dist/commands/trade.js +933 -0
  29. package/dist/commands/trade.js.map +1 -0
  30. package/dist/commands/transfer.d.ts +51 -0
  31. package/dist/commands/transfer.d.ts.map +1 -0
  32. package/dist/commands/transfer.js +728 -0
  33. package/dist/commands/transfer.js.map +1 -0
  34. package/dist/commands/wallet.d.ts +111 -0
  35. package/dist/commands/wallet.d.ts.map +1 -0
  36. package/dist/commands/wallet.js +716 -0
  37. package/dist/commands/wallet.js.map +1 -0
  38. package/dist/contracts/erc20.d.ts +72 -0
  39. package/dist/contracts/erc20.d.ts.map +1 -0
  40. package/dist/contracts/erc20.js +55 -0
  41. package/dist/contracts/erc20.js.map +1 -0
  42. package/dist/contracts/fourmemeMmRouter.d.ts +68 -0
  43. package/dist/contracts/fourmemeMmRouter.d.ts.map +1 -0
  44. package/dist/contracts/fourmemeMmRouter.js +48 -0
  45. package/dist/contracts/fourmemeMmRouter.js.map +1 -0
  46. package/dist/contracts/pancakeRouter.d.ts +73 -0
  47. package/dist/contracts/pancakeRouter.d.ts.map +1 -0
  48. package/dist/contracts/pancakeRouter.js +50 -0
  49. package/dist/contracts/pancakeRouter.js.map +1 -0
  50. package/dist/contracts/tokenManager2.d.ts +193 -0
  51. package/dist/contracts/tokenManager2.d.ts.map +1 -0
  52. package/dist/contracts/tokenManager2.js +108 -0
  53. package/dist/contracts/tokenManager2.js.map +1 -0
  54. package/dist/contracts/tokenManagerHelper3.d.ts +118 -0
  55. package/dist/contracts/tokenManagerHelper3.d.ts.map +1 -0
  56. package/dist/contracts/tokenManagerHelper3.js +66 -0
  57. package/dist/contracts/tokenManagerHelper3.js.map +1 -0
  58. package/dist/datastore/cache.d.ts +20 -0
  59. package/dist/datastore/cache.d.ts.map +1 -0
  60. package/dist/datastore/cache.js +45 -0
  61. package/dist/datastore/cache.js.map +1 -0
  62. package/dist/datastore/index.d.ts +85 -0
  63. package/dist/datastore/index.d.ts.map +1 -0
  64. package/dist/datastore/index.js +341 -0
  65. package/dist/datastore/index.js.map +1 -0
  66. package/dist/datastore/paths.d.ts +17 -0
  67. package/dist/datastore/paths.d.ts.map +1 -0
  68. package/dist/datastore/paths.js +39 -0
  69. package/dist/datastore/paths.js.map +1 -0
  70. package/dist/datastore/types.d.ts +105 -0
  71. package/dist/datastore/types.d.ts.map +1 -0
  72. package/dist/datastore/types.js +8 -0
  73. package/dist/datastore/types.js.map +1 -0
  74. package/dist/fourmeme/auth.d.ts +22 -0
  75. package/dist/fourmeme/auth.d.ts.map +1 -0
  76. package/dist/fourmeme/auth.js +78 -0
  77. package/dist/fourmeme/auth.js.map +1 -0
  78. package/dist/fourmeme/create.d.ts +31 -0
  79. package/dist/fourmeme/create.d.ts.map +1 -0
  80. package/dist/fourmeme/create.js +111 -0
  81. package/dist/fourmeme/create.js.map +1 -0
  82. package/dist/fourmeme/upload.d.ts +16 -0
  83. package/dist/fourmeme/upload.d.ts.map +1 -0
  84. package/dist/fourmeme/upload.js +52 -0
  85. package/dist/fourmeme/upload.js.map +1 -0
  86. package/dist/lib/bundle.d.ts +51 -0
  87. package/dist/lib/bundle.d.ts.map +1 -0
  88. package/dist/lib/bundle.js +95 -0
  89. package/dist/lib/bundle.js.map +1 -0
  90. package/dist/lib/config.d.ts +58 -0
  91. package/dist/lib/config.d.ts.map +1 -0
  92. package/dist/lib/config.js +183 -0
  93. package/dist/lib/config.js.map +1 -0
  94. package/dist/lib/const.d.ts +165 -0
  95. package/dist/lib/const.d.ts.map +1 -0
  96. package/dist/lib/const.js +98 -0
  97. package/dist/lib/const.js.map +1 -0
  98. package/dist/lib/env.d.ts +14 -0
  99. package/dist/lib/env.d.ts.map +1 -0
  100. package/dist/lib/env.js +18 -0
  101. package/dist/lib/env.js.map +1 -0
  102. package/dist/lib/guards.d.ts +44 -0
  103. package/dist/lib/guards.d.ts.map +1 -0
  104. package/dist/lib/guards.js +65 -0
  105. package/dist/lib/guards.js.map +1 -0
  106. package/dist/lib/identify.d.ts +85 -0
  107. package/dist/lib/identify.d.ts.map +1 -0
  108. package/dist/lib/identify.js +88 -0
  109. package/dist/lib/identify.js.map +1 -0
  110. package/dist/lib/pricing.d.ts +62 -0
  111. package/dist/lib/pricing.d.ts.map +1 -0
  112. package/dist/lib/pricing.js +302 -0
  113. package/dist/lib/pricing.js.map +1 -0
  114. package/dist/lib/routing.d.ts +57 -0
  115. package/dist/lib/routing.d.ts.map +1 -0
  116. package/dist/lib/routing.js +67 -0
  117. package/dist/lib/routing.js.map +1 -0
  118. package/dist/lib/slippage.d.ts +29 -0
  119. package/dist/lib/slippage.d.ts.map +1 -0
  120. package/dist/lib/slippage.js +110 -0
  121. package/dist/lib/slippage.js.map +1 -0
  122. package/dist/lib/tracker.d.ts +68 -0
  123. package/dist/lib/tracker.d.ts.map +1 -0
  124. package/dist/lib/tracker.js +155 -0
  125. package/dist/lib/tracker.js.map +1 -0
  126. package/dist/lib/viem.d.ts +12 -0
  127. package/dist/lib/viem.d.ts.map +1 -0
  128. package/dist/lib/viem.js +44 -0
  129. package/dist/lib/viem.js.map +1 -0
  130. package/dist/lib/wallet-rows.d.ts +30 -0
  131. package/dist/lib/wallet-rows.d.ts.map +1 -0
  132. package/dist/lib/wallet-rows.js +9 -0
  133. package/dist/lib/wallet-rows.js.map +1 -0
  134. package/dist/lib/walletClient.d.ts +16 -0
  135. package/dist/lib/walletClient.d.ts.map +1 -0
  136. package/dist/lib/walletClient.js +26 -0
  137. package/dist/lib/walletClient.js.map +1 -0
  138. package/dist/wallets/groups/encrypt.d.ts +26 -0
  139. package/dist/wallets/groups/encrypt.d.ts.map +1 -0
  140. package/dist/wallets/groups/encrypt.js +52 -0
  141. package/dist/wallets/groups/encrypt.js.map +1 -0
  142. package/dist/wallets/groups/generate.d.ts +19 -0
  143. package/dist/wallets/groups/generate.d.ts.map +1 -0
  144. package/dist/wallets/groups/generate.js +36 -0
  145. package/dist/wallets/groups/generate.js.map +1 -0
  146. package/dist/wallets/groups/store.d.ts +107 -0
  147. package/dist/wallets/groups/store.d.ts.map +1 -0
  148. package/dist/wallets/groups/store.js +254 -0
  149. package/dist/wallets/groups/store.js.map +1 -0
  150. package/package.json +50 -0
  151. package/skills/SKILL.md +187 -0
@@ -0,0 +1,650 @@
1
+ /**
2
+ * `fourmm token` command group — Four.meme token operations.
3
+ *
4
+ * Week 1 scope: `token info` only. The other 4 token commands
5
+ * (create, pool, identify, graduate-status) arrive in Weeks 2-3.
6
+ */
7
+ import { Cli, z } from 'incur';
8
+ import { formatEther, formatUnits, isAddress, getAddress, parseEther, } from 'viem';
9
+ import { privateKeyToAccount } from 'viem/accounts';
10
+ import { TOKEN_MANAGER_HELPER3, TOKEN_MANAGER_V2, isFourmemeNativeAddress, } from '../lib/const.js';
11
+ import { loadConfig } from '../lib/config.js';
12
+ import { tokenManagerHelper3Abi } from '../contracts/tokenManagerHelper3.js';
13
+ import { tokenManager2Abi } from '../contracts/tokenManager2.js';
14
+ import { identifyToken } from '../lib/identify.js';
15
+ import { resolveFourmmPassword } from '../lib/env.js';
16
+ import { makeWalletClient } from '../lib/walletClient.js';
17
+ import { trackInBackground } from '../lib/tracker.js';
18
+ import { getPublicClient } from '../lib/viem.js';
19
+ import { authenticateFourmeme } from '../fourmeme/auth.js';
20
+ import { uploadTokenImage } from '../fourmeme/upload.js';
21
+ import { createTokenOnApi } from '../fourmeme/create.js';
22
+ import { decryptPrivateKey, getGroup } from '../wallets/groups/store.js';
23
+ export const token = Cli.create('token', {
24
+ description: 'Four.meme token operations (info, identify, graduate-status, …)',
25
+ })
26
+ // ============================================================
27
+ // fourmm token identify
28
+ // ============================================================
29
+ .command('identify', {
30
+ description: 'Classify a token (standard / anti-sniper-fee / tax-token / x-mode) via the Four.meme REST API. fourMM will REFUSE to trade tax-token or x-mode.',
31
+ options: z.object({
32
+ ca: z
33
+ .string()
34
+ .regex(/^0x[a-fA-F0-9]{40}$/, 'Expected a 40-hex-char 0x-prefixed address')
35
+ .describe('Token contract address'),
36
+ }),
37
+ examples: [
38
+ {
39
+ options: { ca: '0x802CF8e2673f619c486a2950feE3D24f8A074444' },
40
+ description: 'Classify a Four.meme token',
41
+ },
42
+ ],
43
+ output: z.object({
44
+ ca: z.string(),
45
+ variant: z.string(),
46
+ supported: z.boolean(),
47
+ source: z.string(),
48
+ aiCreator: z.boolean().optional(),
49
+ note: z.string().optional(),
50
+ }),
51
+ async run(c) {
52
+ if (!isAddress(c.options.ca)) {
53
+ return c.error({
54
+ code: 'INVALID_ADDRESS',
55
+ message: `"${c.options.ca}" is not a valid address`,
56
+ });
57
+ }
58
+ const ca = getAddress(c.options.ca);
59
+ const result = await identifyToken(ca);
60
+ // A token is only "supported" when:
61
+ // 1. The Four.meme API gave us a real classification (source === 'api')
62
+ // 2. AND the variant is one of the two we actively trade
63
+ //
64
+ // If source is 'not-found', the token doesn't exist on Four.meme at
65
+ // all — even though identifyToken returns variant='standard' as a
66
+ // default, it's NOT a supported token. Agents that only check the
67
+ // variant field would be misled.
68
+ const supported = result.source === 'api' &&
69
+ (result.variant === 'standard' || result.variant === 'anti-sniper-fee');
70
+ const note = (() => {
71
+ if (result.source === 'not-found') {
72
+ return ('Token is not registered with Four.meme. The address may be ' +
73
+ 'wrong, or this token predates the API we query. fourMM will ' +
74
+ 'refuse to trade it.');
75
+ }
76
+ if (result.source === 'fallback-network') {
77
+ return ('Four.meme API is unreachable — classification defaulted to ' +
78
+ 'standard. Downstream trade commands will validate via RPC ' +
79
+ 'before signing.');
80
+ }
81
+ if (result.variant === 'tax-token') {
82
+ return 'TaxToken is out of scope — fourMM refuses to market-make on TaxTokens because each round-trip costs 2× the fee rate (10% on a 5% tax). This is by design.';
83
+ }
84
+ if (result.variant === 'x-mode') {
85
+ return 'X Mode token uses a special encoded-args buy interface that fourMM does not implement.';
86
+ }
87
+ if (result.variant === 'anti-sniper-fee') {
88
+ return 'Dynamic fee decreases block-by-block post-launch. Slippage should be set higher in the first few blocks.';
89
+ }
90
+ return undefined;
91
+ })();
92
+ const aiCreator = result.raw?.data?.aiCreator ?? false;
93
+ return c.ok({
94
+ ca,
95
+ variant: result.variant,
96
+ supported,
97
+ source: result.source,
98
+ aiCreator,
99
+ ...(note ? { note } : {}),
100
+ }, {
101
+ cta: supported
102
+ ? {
103
+ commands: [
104
+ { command: 'token info', options: { ca }, description: 'See full token metadata' },
105
+ { command: 'query price', options: { token: ca }, description: 'Get current price' },
106
+ ],
107
+ }
108
+ : {
109
+ commands: [
110
+ { command: 'token info', options: { ca }, description: 'Inspect metadata anyway' },
111
+ ],
112
+ },
113
+ });
114
+ },
115
+ })
116
+ // ============================================================
117
+ // fourmm token graduate-status
118
+ // ============================================================
119
+ .command('graduate-status', {
120
+ description: 'Show bonding curve graduation progress — offers / maxOffers / funds / maxFunds / liquidityAdded. Useful for timing exits before a token graduates.',
121
+ options: z.object({
122
+ ca: z
123
+ .string()
124
+ .regex(/^0x[a-fA-F0-9]{40}$/, 'Expected a 40-hex-char 0x-prefixed address')
125
+ .describe('Token contract address'),
126
+ }),
127
+ examples: [
128
+ {
129
+ options: { ca: '0x802CF8e2673f619c486a2950feE3D24f8A074444' },
130
+ description: 'Check graduation progress',
131
+ },
132
+ ],
133
+ output: z.object({
134
+ ca: z.string(),
135
+ offers: z.string(),
136
+ maxOffers: z.string(),
137
+ funds: z.string(),
138
+ maxFunds: z.string(),
139
+ progressBps: z.number(),
140
+ progress: z.string(),
141
+ liquidityAdded: z.boolean(),
142
+ tradeable: z.boolean(),
143
+ }),
144
+ async run(c) {
145
+ if (!isAddress(c.options.ca)) {
146
+ return c.error({
147
+ code: 'INVALID_ADDRESS',
148
+ message: `"${c.options.ca}" is not a valid address`,
149
+ });
150
+ }
151
+ const ca = getAddress(c.options.ca);
152
+ const client = getPublicClient();
153
+ let info;
154
+ try {
155
+ info = await client.readContract({
156
+ address: TOKEN_MANAGER_HELPER3,
157
+ abi: tokenManagerHelper3Abi,
158
+ functionName: 'getTokenInfo',
159
+ args: [ca],
160
+ });
161
+ }
162
+ catch (err) {
163
+ return c.error({
164
+ code: 'RPC_READ_FAILED',
165
+ message: err instanceof Error
166
+ ? `getTokenInfo(${ca}) failed: ${err.message}`
167
+ : `getTokenInfo(${ca}) failed`,
168
+ });
169
+ }
170
+ const version = info[0];
171
+ const launchTime = info[6];
172
+ const offers = info[7];
173
+ const maxOffers = info[8];
174
+ const funds = info[9];
175
+ const maxFunds = info[10];
176
+ const liquidityAdded = info[11];
177
+ if (version === 0n) {
178
+ return c.error({
179
+ code: 'NOT_FOUND',
180
+ message: `Token ${ca} is not registered with Four.meme`,
181
+ });
182
+ }
183
+ // BigInt math for graduation percentage
184
+ const progressBps = maxFunds > 0n ? Number((funds * 10000n) / maxFunds) : 0;
185
+ const progress = `${(progressBps / 100).toFixed(2)}%`;
186
+ const launchTimeUnix = Number(launchTime);
187
+ const nowUnix = Math.floor(Date.now() / 1000);
188
+ const tradeable = launchTime > 0n && launchTimeUnix <= nowUnix;
189
+ return c.ok({
190
+ ca,
191
+ offers: formatUnits(offers, 18),
192
+ maxOffers: formatUnits(maxOffers, 18),
193
+ funds: `${formatEther(funds)} BNB`,
194
+ maxFunds: `${formatEther(maxFunds)} BNB`,
195
+ progressBps,
196
+ progress,
197
+ liquidityAdded,
198
+ tradeable,
199
+ }, {
200
+ cta: liquidityAdded
201
+ ? {
202
+ commands: [
203
+ {
204
+ command: 'query price',
205
+ options: { token: ca },
206
+ description: 'Already graduated — check PancakeSwap price',
207
+ },
208
+ ],
209
+ }
210
+ : {
211
+ commands: [
212
+ {
213
+ command: 'query price',
214
+ options: { token: ca },
215
+ description: 'Current bonding curve price',
216
+ },
217
+ ],
218
+ },
219
+ });
220
+ },
221
+ })
222
+ // ============================================================
223
+ // fourmm token info
224
+ // ============================================================
225
+ .command('info', {
226
+ description: 'Fetch live on-chain info for a Four.meme token via TokenManagerHelper3.',
227
+ options: z.object({
228
+ ca: z
229
+ .string()
230
+ .regex(/^0x[a-fA-F0-9]{40}$/, 'Expected a 40-hex-char 0x-prefixed address')
231
+ .describe('Token contract address'),
232
+ }),
233
+ examples: [
234
+ {
235
+ options: { ca: '0x0000000000000000000000000000000000004444' },
236
+ description: 'Look up a specific Four.meme token',
237
+ },
238
+ ],
239
+ output: z.object({
240
+ ca: z.string(),
241
+ fourmemeNative: z.boolean(),
242
+ version: z.number(),
243
+ tokenManager: z.string(),
244
+ quote: z.string(),
245
+ quoteSymbol: z.string(),
246
+ lastPrice: z.string(),
247
+ tradingFeeRate: z.string(),
248
+ minTradingFee: z.string(),
249
+ launchTime: z.string(),
250
+ launchTimeUnix: z.number(),
251
+ tradeable: z.boolean().describe('True when launchTime has passed. If false, the token exists but trading is not yet open; `trade buy` will revert.'),
252
+ offers: z.string(),
253
+ maxOffers: z.string(),
254
+ funds: z.string(),
255
+ maxFunds: z.string(),
256
+ graduationProgress: z.string(),
257
+ liquidityAdded: z.boolean(),
258
+ }),
259
+ async run(c) {
260
+ if (!isAddress(c.options.ca)) {
261
+ return c.error({
262
+ code: 'INVALID_ADDRESS',
263
+ message: `"${c.options.ca}" is not a valid address`,
264
+ });
265
+ }
266
+ const ca = getAddress(c.options.ca);
267
+ const client = getPublicClient();
268
+ let info;
269
+ try {
270
+ info = await client.readContract({
271
+ address: TOKEN_MANAGER_HELPER3,
272
+ abi: tokenManagerHelper3Abi,
273
+ functionName: 'getTokenInfo',
274
+ args: [ca],
275
+ });
276
+ }
277
+ catch (err) {
278
+ return c.error({
279
+ code: 'RPC_READ_FAILED',
280
+ message: err instanceof Error
281
+ ? `getTokenInfo(${ca}) failed: ${err.message}`
282
+ : `getTokenInfo(${ca}) failed`,
283
+ });
284
+ }
285
+ const [version, tokenManager, quote, lastPrice, tradingFeeRate, minTradingFee, launchTime, offers, maxOffers, funds, maxFunds, liquidityAdded,] = info;
286
+ // Version 0 usually means "token not registered" — no such token on Four.meme
287
+ if (version === 0n) {
288
+ return c.error({
289
+ code: 'NOT_FOUND',
290
+ message: `Token ${ca} is not registered in Four.meme's TokenManager. Either the address is wrong or the token predates TokenManager V2.`,
291
+ });
292
+ }
293
+ // Quote address(0) means the token is priced in BNB
294
+ const quoteSymbol = quote === '0x0000000000000000000000000000000000000000' ? 'BNB' : 'BEP20';
295
+ // Launch time is Unix seconds. If zero, the token is registered but
296
+ // the launchTime is unset (rare — treat as "not yet tradeable").
297
+ const launchTimeUnix = Number(launchTime);
298
+ const launchIso = launchTime > 0n ? new Date(launchTimeUnix * 1000).toISOString() : '—';
299
+ const nowUnix = Math.floor(Date.now() / 1000);
300
+ const tradeable = launchTime > 0n && launchTimeUnix <= nowUnix;
301
+ // Graduation progress = funds / maxFunds.
302
+ // Both are 18-decimal bigints. At target (24 BNB = 2.4e19 wei) they exceed
303
+ // Number.MAX_SAFE_INTEGER (~9e15), so convert to basis points with BigInt
304
+ // math first, then coerce to Number for display.
305
+ const progress = (() => {
306
+ if (maxFunds === 0n)
307
+ return '—';
308
+ const bps = (funds * 10000n) / maxFunds;
309
+ return `${(Number(bps) / 100).toFixed(2)}%`;
310
+ })();
311
+ return c.ok({
312
+ ca,
313
+ fourmemeNative: isFourmemeNativeAddress(ca),
314
+ version: Number(version),
315
+ tokenManager,
316
+ quote,
317
+ quoteSymbol,
318
+ // lastPrice is in wei of quote per token smallest-unit
319
+ lastPrice: lastPrice.toString(),
320
+ tradingFeeRate: `${(Number(tradingFeeRate) / 100).toFixed(2)}%`,
321
+ minTradingFee: `${formatEther(minTradingFee)} ${quoteSymbol}`,
322
+ launchTime: launchIso,
323
+ launchTimeUnix,
324
+ tradeable,
325
+ offers: formatUnits(offers, 18),
326
+ maxOffers: formatUnits(maxOffers, 18),
327
+ funds: `${formatEther(funds)} ${quoteSymbol}`,
328
+ maxFunds: `${formatEther(maxFunds)} ${quoteSymbol}`,
329
+ graduationProgress: progress,
330
+ liquidityAdded,
331
+ }, {
332
+ cta: liquidityAdded
333
+ ? {
334
+ commands: [
335
+ {
336
+ command: 'query price',
337
+ options: { token: ca },
338
+ description: 'This token has graduated — check PancakeSwap price',
339
+ },
340
+ ],
341
+ }
342
+ : {
343
+ commands: [
344
+ {
345
+ command: 'query price',
346
+ options: { token: ca },
347
+ description: 'Check bonding curve price',
348
+ },
349
+ {
350
+ command: 'token graduate-status',
351
+ options: { ca },
352
+ description: 'View detailed graduation progress',
353
+ },
354
+ ],
355
+ },
356
+ });
357
+ },
358
+ })
359
+ // ============================================================
360
+ // fourmm token create
361
+ // ============================================================
362
+ .command('create', {
363
+ description: 'Create a new Four.meme token. Authenticates via REST API, uploads image, registers token, then calls TokenManager2.createToken on-chain. Optionally does a dev-buy immediately after.',
364
+ options: z.object({
365
+ name: z.string().min(1).describe('Token name'),
366
+ symbol: z.string().min(1).describe('Token symbol'),
367
+ image: z.string().describe('Path to logo image file'),
368
+ description: z.string().default('').describe('Token description'),
369
+ category: z.enum(['Meme', 'AI', 'Defi', 'Games', 'Infra', 'De-Sci', 'Social', 'Depin', 'Charity', 'Others']).default('Meme'),
370
+ twitter: z.string().optional(),
371
+ website: z.string().optional(),
372
+ telegram: z.string().optional(),
373
+ presetBuy: z.coerce.number().min(0).default(0).describe('BNB for dev buy after creation. NOTE: dev buy is a separate tx (not atomic with create). Set 0 to skip.'),
374
+ devWallet: z.coerce.number().int().positive().optional().describe('Group ID for dev wallet (uses first wallet). Required for on-chain tx.'),
375
+ dryRun: z.boolean().default(false).describe('REST API only — skip on-chain createToken'),
376
+ password: z.string().optional(),
377
+ }),
378
+ output: z.object({
379
+ name: z.string(),
380
+ symbol: z.string(),
381
+ imageUrl: z.string(),
382
+ dryRun: z.boolean(),
383
+ createArg: z.string().optional(),
384
+ txHash: z.string().optional(),
385
+ devBuyTxHash: z.string().optional(),
386
+ message: z.string(),
387
+ }),
388
+ async run(c) {
389
+ // Resolve the dev wallet private key for signing
390
+ const fourmmPassword = resolveFourmmPassword(c.options.password);
391
+ let devPrivateKey;
392
+ let devAddress;
393
+ if (c.options.devWallet) {
394
+ if (!fourmmPassword) {
395
+ return c.error({ code: 'NO_PASSWORD', message: 'Password required for dev wallet' });
396
+ }
397
+ const group = getGroup(fourmmPassword, c.options.devWallet);
398
+ if (!group || group.wallets.length === 0) {
399
+ return c.error({ code: 'GROUP_NOT_FOUND', message: `Dev wallet group ${c.options.devWallet} not found` });
400
+ }
401
+ devPrivateKey = decryptPrivateKey(group.wallets[0], fourmmPassword);
402
+ devAddress = group.wallets[0].address;
403
+ }
404
+ else if (!c.options.dryRun) {
405
+ return c.error({
406
+ code: 'DEV_WALLET_REQUIRED',
407
+ message: 'On-chain token creation requires --dev-wallet <groupId>. Use --dry-run to test REST flow only.',
408
+ });
409
+ }
410
+ // Step 0: Validate image exists BEFORE burning an auth nonce
411
+ const imgFs = await import('node:fs');
412
+ if (!imgFs.existsSync(c.options.image)) {
413
+ return c.error({
414
+ code: 'IMAGE_NOT_FOUND',
415
+ message: `Image file not found: ${c.options.image}`,
416
+ });
417
+ }
418
+ // Step 1: Authenticate with Four.meme
419
+ let auth;
420
+ try {
421
+ if (!devPrivateKey) {
422
+ return c.error({ code: 'NO_KEY', message: 'Dev wallet private key required for REST auth' });
423
+ }
424
+ auth = await authenticateFourmeme(devPrivateKey);
425
+ }
426
+ catch (err) {
427
+ return c.error({
428
+ code: 'AUTH_FAILED',
429
+ message: err instanceof Error ? err.message : String(err),
430
+ });
431
+ }
432
+ // Step 2: Upload image
433
+ let imageUrl;
434
+ try {
435
+ const result = await uploadTokenImage(c.options.image, auth.accessToken);
436
+ imageUrl = result.imageUrl;
437
+ }
438
+ catch (err) {
439
+ return c.error({
440
+ code: 'UPLOAD_FAILED',
441
+ message: err instanceof Error ? err.message : String(err),
442
+ });
443
+ }
444
+ // Step 3: Register token via REST API
445
+ let createResult;
446
+ try {
447
+ createResult = await createTokenOnApi({
448
+ name: c.options.name,
449
+ symbol: c.options.symbol,
450
+ description: c.options.description,
451
+ imageUrl,
452
+ category: c.options.category,
453
+ twitter: c.options.twitter,
454
+ website: c.options.website,
455
+ telegram: c.options.telegram,
456
+ preSale: c.options.presetBuy > 0 ? c.options.presetBuy.toString() : '0',
457
+ }, auth.accessToken);
458
+ }
459
+ catch (err) {
460
+ return c.error({
461
+ code: 'CREATE_API_FAILED',
462
+ message: err instanceof Error ? err.message : String(err),
463
+ });
464
+ }
465
+ if (c.options.dryRun) {
466
+ return c.ok({
467
+ name: c.options.name,
468
+ symbol: c.options.symbol,
469
+ imageUrl,
470
+ dryRun: true,
471
+ createArg: createResult.createArg.slice(0, 40) + '...',
472
+ message: 'REST API succeeded. Pass without --dry-run to submit on-chain.',
473
+ });
474
+ }
475
+ // Step 4: On-chain createToken
476
+ const config = loadConfig();
477
+ const account = privateKeyToAccount(devPrivateKey);
478
+ const walletClient = makeWalletClient(account, config);
479
+ const chain = walletClient.chain;
480
+ const publicClient = getPublicClient();
481
+ let txHash;
482
+ try {
483
+ txHash = await walletClient.writeContract({
484
+ address: TOKEN_MANAGER_V2,
485
+ abi: tokenManager2Abi,
486
+ functionName: 'createToken',
487
+ args: [
488
+ createResult.createArg,
489
+ createResult.signature,
490
+ ],
491
+ value: parseEther('0.01'), // creation fee (~0.005 BNB + buffer)
492
+ chain,
493
+ account,
494
+ });
495
+ }
496
+ catch (err) {
497
+ return c.error({
498
+ code: 'CREATE_TX_FAILED',
499
+ message: err instanceof Error ? err.message : String(err),
500
+ });
501
+ }
502
+ // Step 5: Optional dev buy (separate tx — not atomic with create)
503
+ let devBuyTxHash;
504
+ if (c.options.presetBuy > 0) {
505
+ // Wait for creation to confirm so we can parse the token address
506
+ try {
507
+ const receipt = await publicClient.waitForTransactionReceipt({
508
+ hash: txHash,
509
+ timeout: 60_000,
510
+ });
511
+ // Parse TokenCreate event using viem's type-safe ABI decoder.
512
+ // Much more robust than raw hex offset parsing.
513
+ const { parseEventLogs } = await import('viem');
514
+ const createLogs = parseEventLogs({
515
+ abi: tokenManager2Abi,
516
+ logs: receipt.logs,
517
+ eventName: 'TokenCreate',
518
+ });
519
+ const newTokenAddress = createLogs.length > 0
520
+ ? createLogs[0].args.token
521
+ : undefined;
522
+ if (newTokenAddress) {
523
+ // Dev buy: use buyTokenAMAP on the new token
524
+ const buyBnbWei = parseEther(c.options.presetBuy.toString());
525
+ try {
526
+ const buyHash = await walletClient.writeContract({
527
+ address: TOKEN_MANAGER_V2,
528
+ abi: tokenManager2Abi,
529
+ functionName: 'buyTokenAMAP',
530
+ args: [newTokenAddress, buyBnbWei, 0n], // minAmount=0 for immediate dev buy
531
+ value: buyBnbWei,
532
+ chain,
533
+ account,
534
+ });
535
+ devBuyTxHash = buyHash;
536
+ trackInBackground(publicClient, buyHash, {
537
+ ca: newTokenAddress,
538
+ groupId: c.options.devWallet,
539
+ walletAddress: account.address,
540
+ txType: 'buy',
541
+ knownAmountBnb: -c.options.presetBuy,
542
+ });
543
+ }
544
+ catch (buyErr) {
545
+ // Dev buy failed but create succeeded — report both
546
+ devBuyTxHash = `FAILED: ${buyErr instanceof Error ? buyErr.message : String(buyErr)}`;
547
+ }
548
+ }
549
+ }
550
+ catch {
551
+ // Creation not confirmed in time — skip dev buy
552
+ devBuyTxHash = 'SKIPPED: creation receipt timeout';
553
+ }
554
+ }
555
+ return c.ok({
556
+ name: c.options.name,
557
+ symbol: c.options.symbol,
558
+ imageUrl,
559
+ dryRun: false,
560
+ txHash,
561
+ devBuyTxHash,
562
+ message: devBuyTxHash
563
+ ? `Token created + dev buy broadcast. Check BSCScan for details.`
564
+ : `Token creation tx broadcast. Check BSCScan for the new token address.`,
565
+ }, {
566
+ cta: {
567
+ commands: [
568
+ {
569
+ command: 'query balance',
570
+ options: { address: devAddress ?? '' },
571
+ description: 'Check dev wallet balance after creation',
572
+ },
573
+ ],
574
+ },
575
+ });
576
+ },
577
+ })
578
+ // ============================================================
579
+ // fourmm token pool
580
+ // ============================================================
581
+ .command('pool', {
582
+ description: 'Show pool / liquidity info for a token via TokenManagerHelper3.',
583
+ options: z.object({
584
+ ca: z.string().regex(/^0x[a-fA-F0-9]{40}$/).describe('Token contract address'),
585
+ }),
586
+ output: z.object({
587
+ ca: z.string(),
588
+ version: z.number(),
589
+ tokenManager: z.string(),
590
+ quote: z.string(),
591
+ quoteSymbol: z.string(),
592
+ lastPrice: z.string(),
593
+ tradingFeeRate: z.string(),
594
+ funds: z.string(),
595
+ maxFunds: z.string(),
596
+ offers: z.string(),
597
+ maxOffers: z.string(),
598
+ liquidityAdded: z.boolean(),
599
+ graduationProgress: z.string(),
600
+ }),
601
+ async run(c) {
602
+ if (!isAddress(c.options.ca)) {
603
+ return c.error({ code: 'INVALID_ADDRESS', message: `"${c.options.ca}" is not a valid address` });
604
+ }
605
+ const ca = getAddress(c.options.ca);
606
+ const client = getPublicClient();
607
+ let info;
608
+ try {
609
+ info = await client.readContract({
610
+ address: TOKEN_MANAGER_HELPER3,
611
+ abi: tokenManagerHelper3Abi,
612
+ functionName: 'getTokenInfo',
613
+ args: [ca],
614
+ });
615
+ }
616
+ catch (err) {
617
+ return c.error({ code: 'RPC_READ_FAILED', message: err instanceof Error ? `getTokenInfo(${ca}) failed: ${err.message}` : `getTokenInfo(${ca}) failed` });
618
+ }
619
+ const [version, tokenManager, quote, lastPrice, tradingFeeRate, _minFee, _launchTime, offers, maxOffers, funds, maxFunds, liquidityAdded] = info;
620
+ if (version === 0n) {
621
+ return c.error({ code: 'NOT_FOUND', message: `Token ${ca} is not registered with Four.meme` });
622
+ }
623
+ const quoteSymbol = quote === '0x0000000000000000000000000000000000000000' ? 'BNB' : 'BEP20';
624
+ const progressBps = maxFunds > 0n ? Number((funds * 10000n) / maxFunds) : 0;
625
+ const progress = `${(progressBps / 100).toFixed(2)}%`;
626
+ return c.ok({
627
+ ca,
628
+ version: Number(version),
629
+ tokenManager,
630
+ quote,
631
+ quoteSymbol,
632
+ lastPrice: lastPrice.toString(),
633
+ tradingFeeRate: `${(Number(tradingFeeRate) / 100).toFixed(2)}%`,
634
+ funds: `${formatEther(funds)} ${quoteSymbol}`,
635
+ maxFunds: `${formatEther(maxFunds)} ${quoteSymbol}`,
636
+ offers: formatUnits(offers, 18),
637
+ maxOffers: formatUnits(maxOffers, 18),
638
+ liquidityAdded,
639
+ graduationProgress: progress,
640
+ }, {
641
+ cta: {
642
+ commands: [
643
+ { command: 'query price', options: { token: ca }, description: 'Get current price' },
644
+ { command: 'token graduate-status', options: { ca }, description: 'Detailed graduation progress' },
645
+ ],
646
+ },
647
+ });
648
+ },
649
+ });
650
+ //# sourceMappingURL=token.js.map