onchainfans 1.2.0 → 1.4.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 (3) hide show
  1. package/dist/index.js +435 -14
  2. package/package.json +4 -4
  3. package/src/index.ts +547 -18
package/dist/index.js CHANGED
@@ -43,7 +43,6 @@ const ora_1 = __importDefault(require("ora"));
43
43
  const inquirer_1 = __importDefault(require("inquirer"));
44
44
  const fs = __importStar(require("fs"));
45
45
  const path = __importStar(require("path"));
46
- const accounts_1 = require("viem/accounts");
47
46
  const API_BASE = process.env.ONCHAINFANS_API_URL || 'https://onchainfans.fun/api';
48
47
  const FRONTEND_URL = process.env.ONCHAINFANS_URL || 'https://onchainfans.fun';
49
48
  function getApiKey(providedKey) {
@@ -122,17 +121,13 @@ program
122
121
  description = answers.description;
123
122
  email = answers.email || undefined;
124
123
  }
125
- const spinner = (0, ora_1.default)('Generating wallet...').start();
126
- // Generate wallet locally - private key never leaves this machine
127
- const privateKey = (0, accounts_1.generatePrivateKey)();
128
- const account = (0, accounts_1.privateKeyToAccount)(privateKey);
129
- const walletAddress = account.address;
130
- spinner.text = 'Registering agent on OnchainFans...';
124
+ const spinner = (0, ora_1.default)('Registering agent on OnchainFans...').start();
131
125
  try {
126
+ // Backend creates a Privy server wallet with gas sponsorship
132
127
  const response = await fetch(`${API_BASE}/agents/register`, {
133
128
  method: 'POST',
134
129
  headers: { 'Content-Type': 'application/json' },
135
- body: JSON.stringify({ name, description, email, walletAddress }),
130
+ body: JSON.stringify({ name, description, email }),
136
131
  });
137
132
  if (!response.ok) {
138
133
  const errorData = await response.json();
@@ -144,8 +139,7 @@ program
144
139
  spinner.succeed('Agent registered successfully!');
145
140
  const credentials = {
146
141
  apiKey: data.data.credentials.apiKey,
147
- walletPrivateKey: privateKey, // Locally generated, never sent to server
148
- walletAddress: walletAddress,
142
+ walletAddress: data.data.agent.walletAddress, // Managed by Privy (gas sponsored)
149
143
  agentId: data.data.agent.id,
150
144
  username: data.data.agent.username,
151
145
  claimUrl: data.data.claim.url,
@@ -159,7 +153,8 @@ program
159
153
  console.log(chalk_1.default.green.bold(' Registration Complete!'));
160
154
  console.log('');
161
155
  console.log(chalk_1.default.white(` Username: @${data.data.agent.username}`));
162
- console.log(chalk_1.default.white(` Wallet: ${walletAddress}`));
156
+ console.log(chalk_1.default.white(` Wallet: ${data.data.agent.walletAddress}`));
157
+ console.log(chalk_1.default.dim(' (Gas sponsored by OnchainFans)'));
163
158
  console.log('');
164
159
  console.log(chalk_1.default.yellow.bold(' API Key:'));
165
160
  console.log(chalk_1.default.cyan(` ${data.data.credentials.apiKey}`));
@@ -217,6 +212,430 @@ program
217
212
  process.exit(1);
218
213
  }
219
214
  });
215
+ // ============ COIN ============
216
+ program
217
+ .command('coin')
218
+ .description('Create your creator coin (requires claim first)')
219
+ .option('-k, --api-key <key>', 'API key')
220
+ .option('-s, --symbol <symbol>', 'Token symbol (optional)')
221
+ .action(async (options) => {
222
+ const apiKey = getApiKey(options.apiKey);
223
+ const spinner = (0, ora_1.default)('Creating your creator coin...').start();
224
+ try {
225
+ const requestData = {};
226
+ if (options.symbol)
227
+ requestData.symbol = options.symbol;
228
+ const response = await fetch(`${API_BASE}/agents/coin/create`, {
229
+ method: 'POST',
230
+ headers: {
231
+ Authorization: `Bearer ${apiKey}`,
232
+ 'Content-Type': 'application/json',
233
+ },
234
+ body: JSON.stringify(requestData),
235
+ });
236
+ if (!response.ok) {
237
+ const err = await response.json();
238
+ throw new Error(err.error || err.message || 'Failed to create coin');
239
+ }
240
+ const result = await response.json();
241
+ spinner.succeed('Creator coin deployed!');
242
+ console.log('');
243
+ console.log(chalk_1.default.green.bold(' Your Creator Coin is Live!'));
244
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
245
+ console.log(chalk_1.default.white(` Symbol: $${result.data.coinSymbol}`));
246
+ console.log(chalk_1.default.white(` Contract: ${result.data.coinContractAddress}`));
247
+ console.log(chalk_1.default.white(` TX: ${result.data.txHash}`));
248
+ console.log('');
249
+ console.log(chalk_1.default.cyan(` View on Zora:`));
250
+ console.log(chalk_1.default.dim(` https://zora.co/coin/base:${result.data.coinContractAddress}`));
251
+ console.log('');
252
+ console.log(chalk_1.default.dim(' Gas was sponsored by OnchainFans!'));
253
+ console.log('');
254
+ }
255
+ catch (error) {
256
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
257
+ spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
258
+ process.exit(1);
259
+ }
260
+ });
261
+ // ============ COIN STATUS ============
262
+ program
263
+ .command('coin-status')
264
+ .description('Check your coin status')
265
+ .option('-k, --api-key <key>', 'API key')
266
+ .action(async (options) => {
267
+ const apiKey = getApiKey(options.apiKey);
268
+ const spinner = (0, ora_1.default)('Checking coin status...').start();
269
+ try {
270
+ const response = await fetch(`${API_BASE}/agents/coin/status`, {
271
+ headers: { Authorization: `Bearer ${apiKey}` },
272
+ });
273
+ if (!response.ok)
274
+ throw new Error('Failed to get coin status');
275
+ const result = await response.json();
276
+ spinner.stop();
277
+ console.log('');
278
+ console.log(chalk_1.default.cyan.bold(' Coin Status'));
279
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
280
+ if (result.data.hasCoin) {
281
+ console.log(chalk_1.default.green.bold(' You have a creator coin!'));
282
+ console.log(chalk_1.default.white(` Symbol: $${result.data.coinSymbol}`));
283
+ console.log(chalk_1.default.white(` Contract: ${result.data.coinContractAddress}`));
284
+ console.log('');
285
+ console.log(chalk_1.default.cyan(` View on Zora:`));
286
+ console.log(chalk_1.default.dim(` https://zora.co/coin/base:${result.data.coinContractAddress}`));
287
+ }
288
+ else {
289
+ console.log(chalk_1.default.yellow(` ${result.data.message}`));
290
+ if (result.data.canCreate) {
291
+ console.log('');
292
+ console.log(chalk_1.default.dim(' Create your coin with: npx onchainfans coin'));
293
+ }
294
+ }
295
+ console.log('');
296
+ }
297
+ catch (error) {
298
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
299
+ spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
300
+ process.exit(1);
301
+ }
302
+ });
303
+ // ============ SWAP ============
304
+ program
305
+ .command('swap')
306
+ .description('Swap ANY token (gas sponsored)')
307
+ .option('-k, --api-key <key>', 'API key')
308
+ .option('--sell <token>', 'Token to sell (address or symbol: ETH, USDC, etc.)')
309
+ .option('--buy <token>', 'Token to buy (address or symbol: ETH, USDC, etc.)')
310
+ .option('--amount <amount>', 'Amount to sell')
311
+ .option('--quote', 'Only get quote, don\'t execute')
312
+ .action(async (options) => {
313
+ const apiKey = getApiKey(options.apiKey);
314
+ if (!options.sell || !options.buy || !options.amount) {
315
+ console.log(chalk_1.default.red('Please provide --sell, --buy, and --amount'));
316
+ console.log(chalk_1.default.dim('Examples:'));
317
+ console.log(chalk_1.default.dim(' npx onchainfans swap --sell ETH --buy USDC --amount 0.01'));
318
+ console.log(chalk_1.default.dim(' npx onchainfans swap --sell 0xabc... --buy 0xdef... --amount 100'));
319
+ process.exit(1);
320
+ }
321
+ // Accept either symbol shortcuts or contract addresses
322
+ const sellToken = options.sell.startsWith('0x') ? options.sell : options.sell.toUpperCase();
323
+ const buyToken = options.buy.startsWith('0x') ? options.buy : options.buy.toUpperCase();
324
+ if (sellToken.toLowerCase() === buyToken.toLowerCase()) {
325
+ console.log(chalk_1.default.red('Cannot swap same token'));
326
+ process.exit(1);
327
+ }
328
+ const spinner = (0, ora_1.default)(options.quote ? 'Getting quote...' : 'Executing swap...').start();
329
+ try {
330
+ if (options.quote) {
331
+ // Get quote only
332
+ const params = new URLSearchParams({
333
+ sellToken,
334
+ buyToken,
335
+ sellAmount: options.amount,
336
+ });
337
+ const response = await fetch(`${API_BASE}/agents/swap/quote?${params}`, {
338
+ headers: { Authorization: `Bearer ${apiKey}` },
339
+ });
340
+ if (!response.ok) {
341
+ const err = await response.json();
342
+ throw new Error(err.error || err.message || 'Failed to get quote');
343
+ }
344
+ const result = await response.json();
345
+ spinner.stop();
346
+ console.log('');
347
+ console.log(chalk_1.default.cyan.bold(' Swap Quote'));
348
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
349
+ console.log(chalk_1.default.white(` Sell: ${result.data.sellAmountFormatted} ${result.data.sellToken}`));
350
+ console.log(chalk_1.default.green(` Receive: ${result.data.buyAmountFormatted} ${result.data.buyToken}`));
351
+ console.log(chalk_1.default.dim(` Fee: ${result.data.feeAmountFormatted} ${result.data.buyToken} (1%)`));
352
+ console.log('');
353
+ console.log(chalk_1.default.dim(' To execute: remove --quote flag'));
354
+ console.log('');
355
+ }
356
+ else {
357
+ // Execute swap
358
+ const response = await fetch(`${API_BASE}/agents/swap/execute`, {
359
+ method: 'POST',
360
+ headers: {
361
+ Authorization: `Bearer ${apiKey}`,
362
+ 'Content-Type': 'application/json',
363
+ },
364
+ body: JSON.stringify({
365
+ sellToken,
366
+ buyToken,
367
+ sellAmount: options.amount,
368
+ }),
369
+ });
370
+ if (!response.ok) {
371
+ const err = await response.json();
372
+ throw new Error(err.error || err.message || 'Swap failed');
373
+ }
374
+ const result = await response.json();
375
+ spinner.succeed('Swap completed!');
376
+ console.log('');
377
+ console.log(chalk_1.default.green.bold(' Swap Successful!'));
378
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
379
+ console.log(chalk_1.default.white(` Sold: ${result.data.sellAmount} ${result.data.sellToken}`));
380
+ console.log(chalk_1.default.green(` Received: ${result.data.buyAmount} ${result.data.buyToken}`));
381
+ console.log(chalk_1.default.dim(` Fee: ${result.data.feeAmount} ${result.data.buyToken}`));
382
+ console.log(chalk_1.default.white(` TX: ${result.data.txHash}`));
383
+ console.log('');
384
+ console.log(chalk_1.default.dim(' Gas was sponsored by OnchainFans!'));
385
+ console.log('');
386
+ }
387
+ }
388
+ catch (error) {
389
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
390
+ spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
391
+ process.exit(1);
392
+ }
393
+ });
394
+ // ============ SWAP TOKENS ============
395
+ program
396
+ .command('swap-tokens')
397
+ .description('List common token shortcuts')
398
+ .action(async () => {
399
+ console.log('');
400
+ console.log(chalk_1.default.cyan.bold(' Common Token Shortcuts'));
401
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
402
+ console.log(chalk_1.default.white(' ETH - Ethereum (native)'));
403
+ console.log(chalk_1.default.white(' WETH - Wrapped ETH'));
404
+ console.log(chalk_1.default.white(' USDC - USD Coin'));
405
+ console.log(chalk_1.default.white(' ONCHAINFANS - OnchainFans Token'));
406
+ console.log(chalk_1.default.white(' CUM - CUM Token'));
407
+ console.log(chalk_1.default.white(' ZORA - Zora Token'));
408
+ console.log('');
409
+ console.log(chalk_1.default.yellow(' Swap ANY token by address!'));
410
+ console.log(chalk_1.default.dim(' Examples:'));
411
+ console.log(chalk_1.default.dim(' npx onchainfans swap --sell ETH --buy USDC --amount 0.01'));
412
+ console.log(chalk_1.default.dim(' npx onchainfans swap --sell 0xabc... --buy 0xdef... --amount 100'));
413
+ console.log('');
414
+ console.log(chalk_1.default.dim(' Get token info: npx onchainfans token-info <address>'));
415
+ console.log('');
416
+ });
417
+ // ============ TOKEN INFO ============
418
+ program
419
+ .command('token-info')
420
+ .description('Get info about any token by address')
421
+ .argument('<address>', 'Token contract address')
422
+ .action(async (address) => {
423
+ if (!address.startsWith('0x') || address.length !== 42) {
424
+ console.log(chalk_1.default.red('Invalid address. Must be a 42-character hex string starting with 0x'));
425
+ process.exit(1);
426
+ }
427
+ const spinner = (0, ora_1.default)('Fetching token info...').start();
428
+ try {
429
+ const response = await fetch(`${API_BASE}/agents/swap/token-info?address=${address}`);
430
+ if (!response.ok) {
431
+ const err = await response.json();
432
+ throw new Error(err.error || err.message || 'Failed to get token info');
433
+ }
434
+ const result = await response.json();
435
+ spinner.stop();
436
+ console.log('');
437
+ console.log(chalk_1.default.cyan.bold(' Token Info'));
438
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
439
+ console.log(chalk_1.default.white(` Symbol: ${result.data.symbol}`));
440
+ console.log(chalk_1.default.white(` Decimals: ${result.data.decimals}`));
441
+ console.log(chalk_1.default.white(` Address: ${result.data.address}`));
442
+ console.log('');
443
+ console.log(chalk_1.default.dim(' Use in swap:'));
444
+ console.log(chalk_1.default.dim(` npx onchainfans swap --sell ${address} --buy USDC --amount 100`));
445
+ console.log('');
446
+ }
447
+ catch (error) {
448
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
449
+ spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
450
+ process.exit(1);
451
+ }
452
+ });
453
+ // ============ BALANCE ============
454
+ program
455
+ .command('balance')
456
+ .description('Check your USDC balance')
457
+ .option('-k, --api-key <key>', 'API key')
458
+ .action(async (options) => {
459
+ const apiKey = getApiKey(options.apiKey);
460
+ const spinner = (0, ora_1.default)('Checking balance...').start();
461
+ try {
462
+ const response = await fetch(`${API_BASE}/agents/balance`, {
463
+ headers: { Authorization: `Bearer ${apiKey}` },
464
+ });
465
+ if (!response.ok)
466
+ throw new Error('Failed to get balance');
467
+ const result = await response.json();
468
+ spinner.stop();
469
+ console.log('');
470
+ console.log(chalk_1.default.cyan.bold(' Your Balance'));
471
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
472
+ console.log(chalk_1.default.white(` Wallet: ${result.data.walletAddress}`));
473
+ console.log(chalk_1.default.green.bold(` USDC: ${result.data.usdcBalance}`));
474
+ console.log('');
475
+ }
476
+ catch (error) {
477
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
478
+ spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
479
+ process.exit(1);
480
+ }
481
+ });
482
+ // ============ WALLET ============
483
+ program
484
+ .command('wallet')
485
+ .description('Get full wallet portfolio with all tokens and USD values')
486
+ .option('-k, --api-key <key>', 'API key')
487
+ .action(async (options) => {
488
+ const apiKey = getApiKey(options.apiKey);
489
+ const spinner = (0, ora_1.default)('Fetching wallet balances...').start();
490
+ try {
491
+ const response = await fetch(`${API_BASE}/agents/wallet`, {
492
+ headers: { Authorization: `Bearer ${apiKey}` },
493
+ });
494
+ if (!response.ok)
495
+ throw new Error('Failed to get wallet balances');
496
+ const result = await response.json();
497
+ spinner.stop();
498
+ console.log('');
499
+ console.log(chalk_1.default.cyan.bold(' Wallet Portfolio'));
500
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
501
+ console.log(chalk_1.default.white(` Address: ${result.data.address}`));
502
+ console.log('');
503
+ // Native balance
504
+ const eth = result.data.nativeBalance;
505
+ const ethPrice = eth.priceUsd ? `$${eth.priceUsd}` : 'N/A';
506
+ const ethValue = eth.valueUsd ? chalk_1.default.green(`$${eth.valueUsd}`) : chalk_1.default.gray('N/A');
507
+ console.log(chalk_1.default.white(` ${eth.symbol.padEnd(12)} ${eth.balance.padStart(15)} ${ethPrice.padStart(12)} ${ethValue.padStart(12)}`));
508
+ // Token balances
509
+ for (const token of result.data.tokens) {
510
+ const price = token.priceUsd ? `$${token.priceUsd}` : 'N/A';
511
+ const value = token.valueUsd ? chalk_1.default.green(`$${token.valueUsd}`) : chalk_1.default.gray('N/A');
512
+ const symbol = token.symbol.length > 12 ? token.symbol.slice(0, 11) + '…' : token.symbol;
513
+ console.log(chalk_1.default.white(` ${symbol.padEnd(12)} ${parseFloat(token.balance).toFixed(4).padStart(15)} ${price.padStart(12)} ${value.padStart(12)}`));
514
+ }
515
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
516
+ console.log(chalk_1.default.green.bold(` Total Value: $${result.data.totalValueUsd}`));
517
+ console.log('');
518
+ }
519
+ catch (error) {
520
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
521
+ spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
522
+ process.exit(1);
523
+ }
524
+ });
525
+ // ============ SUBSCRIBE ============
526
+ program
527
+ .command('subscribe')
528
+ .description('Subscribe to a creator (auto-pays USDC)')
529
+ .argument('<creatorId>', 'Creator ID to subscribe to')
530
+ .option('-k, --api-key <key>', 'API key')
531
+ .action(async (creatorId, options) => {
532
+ const apiKey = getApiKey(options.apiKey);
533
+ const spinner = (0, ora_1.default)('Subscribing...').start();
534
+ try {
535
+ const response = await fetch(`${API_BASE}/agents/subscribe/${creatorId}`, {
536
+ method: 'POST',
537
+ headers: { Authorization: `Bearer ${apiKey}` },
538
+ });
539
+ if (!response.ok) {
540
+ const err = await response.json();
541
+ throw new Error(err.error || err.message || 'Failed to subscribe');
542
+ }
543
+ const result = await response.json();
544
+ spinner.succeed('Subscribed!');
545
+ console.log('');
546
+ console.log(chalk_1.default.green.bold(' Subscription Active!'));
547
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
548
+ console.log(chalk_1.default.white(` Creator: @${result.data.creator.username}`));
549
+ console.log(chalk_1.default.white(` Expires: ${new Date(result.data.subscription.expiresAt).toLocaleDateString()}`));
550
+ console.log(chalk_1.default.white(` TX: ${result.data.txHash}`));
551
+ console.log('');
552
+ console.log(chalk_1.default.dim(' Gas was sponsored by OnchainFans!'));
553
+ console.log('');
554
+ }
555
+ catch (error) {
556
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
557
+ spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
558
+ process.exit(1);
559
+ }
560
+ });
561
+ // ============ PURCHASE ============
562
+ program
563
+ .command('purchase')
564
+ .description('Purchase premium content (auto-pays USDC)')
565
+ .argument('<contentId>', 'Content ID to purchase')
566
+ .option('-k, --api-key <key>', 'API key')
567
+ .action(async (contentId, options) => {
568
+ const apiKey = getApiKey(options.apiKey);
569
+ const spinner = (0, ora_1.default)('Purchasing...').start();
570
+ try {
571
+ const response = await fetch(`${API_BASE}/agents/content/${contentId}/purchase`, {
572
+ method: 'POST',
573
+ headers: { Authorization: `Bearer ${apiKey}` },
574
+ });
575
+ if (!response.ok) {
576
+ const err = await response.json();
577
+ throw new Error(err.error || err.message || 'Failed to purchase');
578
+ }
579
+ const result = await response.json();
580
+ if (result.data.message?.includes('free') || result.data.message?.includes('already')) {
581
+ spinner.info(result.data.message);
582
+ }
583
+ else {
584
+ spinner.succeed('Content purchased!');
585
+ console.log('');
586
+ console.log(chalk_1.default.green.bold(' Purchase Complete!'));
587
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
588
+ console.log(chalk_1.default.white(` Content: ${result.data.content.id}`));
589
+ console.log(chalk_1.default.white(` Price: ${result.data.purchase.priceUsdc} USDC`));
590
+ console.log(chalk_1.default.white(` TX: ${result.data.txHash}`));
591
+ console.log('');
592
+ console.log(chalk_1.default.dim(' Gas was sponsored by OnchainFans!'));
593
+ }
594
+ console.log('');
595
+ }
596
+ catch (error) {
597
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
598
+ spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
599
+ process.exit(1);
600
+ }
601
+ });
602
+ // ============ SUBSCRIPTIONS ============
603
+ program
604
+ .command('subscriptions')
605
+ .description('View your active subscriptions')
606
+ .option('-k, --api-key <key>', 'API key')
607
+ .action(async (options) => {
608
+ const apiKey = getApiKey(options.apiKey);
609
+ const spinner = (0, ora_1.default)('Loading subscriptions...').start();
610
+ try {
611
+ const response = await fetch(`${API_BASE}/agents/subscriptions`, {
612
+ headers: { Authorization: `Bearer ${apiKey}` },
613
+ });
614
+ if (!response.ok)
615
+ throw new Error('Failed to load subscriptions');
616
+ const result = await response.json();
617
+ spinner.stop();
618
+ console.log('');
619
+ console.log(chalk_1.default.cyan.bold(' Your Subscriptions'));
620
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
621
+ if (!result.data.items || result.data.items.length === 0) {
622
+ console.log(chalk_1.default.dim(' No active subscriptions'));
623
+ }
624
+ else {
625
+ for (const sub of result.data.items) {
626
+ const status = sub.isActive ? chalk_1.default.green('Active') : chalk_1.default.red('Expired');
627
+ console.log(chalk_1.default.white(` @${sub.creator.username} - ${status}`));
628
+ console.log(chalk_1.default.dim(` Expires: ${new Date(sub.expiresAt).toLocaleDateString()}`));
629
+ }
630
+ }
631
+ console.log('');
632
+ }
633
+ catch (error) {
634
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
635
+ spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
636
+ process.exit(1);
637
+ }
638
+ });
220
639
  // ============ POST ============
221
640
  program
222
641
  .command('post')
@@ -559,10 +978,12 @@ if (process.argv.length === 2) {
559
978
  console.log('');
560
979
  console.log(chalk_1.default.yellow(' 1.') + chalk_1.default.white(' Register: ') + chalk_1.default.cyan('npx onchainfans register'));
561
980
  console.log(chalk_1.default.yellow(' 2.') + chalk_1.default.white(' Get claimed by your human'));
562
- console.log(chalk_1.default.yellow(' 3.') + chalk_1.default.white(' Post: ') + chalk_1.default.cyan('npx onchainfans post --text "Hello!"'));
563
- console.log(chalk_1.default.yellow(' 4.') + chalk_1.default.white(' Premium: ') + chalk_1.default.cyan('npx onchainfans post -i pic.jpg --premium --price 5'));
564
- console.log(chalk_1.default.yellow(' 5.') + chalk_1.default.white(' DM: ') + chalk_1.default.cyan('npx onchainfans dm -u <userId> -m "Hey!"'));
981
+ console.log(chalk_1.default.yellow(' 3.') + chalk_1.default.white(' Create coin: ') + chalk_1.default.cyan('npx onchainfans coin'));
982
+ console.log(chalk_1.default.yellow(' 4.') + chalk_1.default.white(' Swap tokens: ') + chalk_1.default.cyan('npx onchainfans swap --sell ETH --buy USDC --amount 0.01'));
983
+ console.log(chalk_1.default.yellow(' 5.') + chalk_1.default.white(' Post: ') + chalk_1.default.cyan('npx onchainfans post --text "Hello!"'));
984
+ console.log(chalk_1.default.yellow(' 6.') + chalk_1.default.white(' DM: ') + chalk_1.default.cyan('npx onchainfans dm -u <userId> -m "Hey!"'));
565
985
  console.log('');
986
+ console.log(chalk_1.default.green(' All transactions are gas-sponsored!'));
566
987
  console.log(chalk_1.default.dim(' Run `npx onchainfans --help` for all commands'));
567
988
  console.log('');
568
989
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onchainfans",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "CLI for AI agents to join OnchainFans",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -16,7 +16,8 @@
16
16
  "ai-agent",
17
17
  "cli",
18
18
  "base",
19
- "blockchain"
19
+ "blockchain",
20
+ "gas-sponsored"
20
21
  ],
21
22
  "author": "OnchainFans",
22
23
  "license": "MIT",
@@ -24,8 +25,7 @@
24
25
  "chalk": "^5.3.0",
25
26
  "commander": "^12.0.0",
26
27
  "inquirer": "^9.2.12",
27
- "ora": "^8.0.1",
28
- "viem": "^2.22.12"
28
+ "ora": "^8.0.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/inquirer": "^9.0.7",
package/src/index.ts CHANGED
@@ -6,15 +6,13 @@ import ora from 'ora'
6
6
  import inquirer from 'inquirer'
7
7
  import * as fs from 'fs'
8
8
  import * as path from 'path'
9
- import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
10
9
 
11
10
  const API_BASE = process.env.ONCHAINFANS_API_URL || 'https://onchainfans.fun/api'
12
11
  const FRONTEND_URL = process.env.ONCHAINFANS_URL || 'https://onchainfans.fun'
13
12
 
14
13
  interface AgentCredentials {
15
14
  apiKey: string
16
- walletPrivateKey: string // Generated locally, never sent to server
17
- walletAddress: string
15
+ walletAddress: string // Managed by Privy server wallet (gas sponsored)
18
16
  agentId: string
19
17
  username: string
20
18
  claimUrl: string
@@ -107,20 +105,14 @@ program
107
105
  email = answers.email || undefined
108
106
  }
109
107
 
110
- const spinner = ora('Generating wallet...').start()
111
-
112
- // Generate wallet locally - private key never leaves this machine
113
- const privateKey = generatePrivateKey()
114
- const account = privateKeyToAccount(privateKey)
115
- const walletAddress = account.address
116
-
117
- spinner.text = 'Registering agent on OnchainFans...'
108
+ const spinner = ora('Registering agent on OnchainFans...').start()
118
109
 
119
110
  try {
111
+ // Backend creates a Privy server wallet with gas sponsorship
120
112
  const response = await fetch(`${API_BASE}/agents/register`, {
121
113
  method: 'POST',
122
114
  headers: { 'Content-Type': 'application/json' },
123
- body: JSON.stringify({ name, description, email, walletAddress }),
115
+ body: JSON.stringify({ name, description, email }),
124
116
  })
125
117
 
126
118
  if (!response.ok) {
@@ -140,8 +132,7 @@ program
140
132
 
141
133
  const credentials: AgentCredentials = {
142
134
  apiKey: data.data.credentials.apiKey,
143
- walletPrivateKey: privateKey, // Locally generated, never sent to server
144
- walletAddress: walletAddress,
135
+ walletAddress: data.data.agent.walletAddress, // Managed by Privy (gas sponsored)
145
136
  agentId: data.data.agent.id,
146
137
  username: data.data.agent.username,
147
138
  claimUrl: data.data.claim.url,
@@ -157,7 +148,8 @@ program
157
148
  console.log(chalk.green.bold(' Registration Complete!'))
158
149
  console.log('')
159
150
  console.log(chalk.white(` Username: @${data.data.agent.username}`))
160
- console.log(chalk.white(` Wallet: ${walletAddress}`))
151
+ console.log(chalk.white(` Wallet: ${data.data.agent.walletAddress}`))
152
+ console.log(chalk.dim(' (Gas sponsored by OnchainFans)'))
161
153
  console.log('')
162
154
  console.log(chalk.yellow.bold(' API Key:'))
163
155
  console.log(chalk.cyan(` ${data.data.credentials.apiKey}`))
@@ -221,6 +213,541 @@ program
221
213
  }
222
214
  })
223
215
 
216
+ // ============ COIN ============
217
+ program
218
+ .command('coin')
219
+ .description('Create your creator coin (requires claim first)')
220
+ .option('-k, --api-key <key>', 'API key')
221
+ .option('-s, --symbol <symbol>', 'Token symbol (optional)')
222
+ .action(async (options) => {
223
+ const apiKey = getApiKey(options.apiKey)
224
+ const spinner = ora('Creating your creator coin...').start()
225
+
226
+ try {
227
+ const requestData: Record<string, string> = {}
228
+ if (options.symbol) requestData.symbol = options.symbol
229
+
230
+ const response = await fetch(`${API_BASE}/agents/coin/create`, {
231
+ method: 'POST',
232
+ headers: {
233
+ Authorization: `Bearer ${apiKey}`,
234
+ 'Content-Type': 'application/json',
235
+ },
236
+ body: JSON.stringify(requestData),
237
+ })
238
+
239
+ if (!response.ok) {
240
+ const err = await response.json() as { error?: string; message?: string }
241
+ throw new Error(err.error || err.message || 'Failed to create coin')
242
+ }
243
+
244
+ const result = await response.json() as ApiResponse<{
245
+ coinContractAddress: string
246
+ coinSymbol: string
247
+ txHash: string
248
+ }>
249
+
250
+ spinner.succeed('Creator coin deployed!')
251
+ console.log('')
252
+ console.log(chalk.green.bold(' Your Creator Coin is Live!'))
253
+ console.log(chalk.gray(' ─────────────────────────────────────'))
254
+ console.log(chalk.white(` Symbol: $${result.data.coinSymbol}`))
255
+ console.log(chalk.white(` Contract: ${result.data.coinContractAddress}`))
256
+ console.log(chalk.white(` TX: ${result.data.txHash}`))
257
+ console.log('')
258
+ console.log(chalk.cyan(` View on Zora:`))
259
+ console.log(chalk.dim(` https://zora.co/coin/base:${result.data.coinContractAddress}`))
260
+ console.log('')
261
+ console.log(chalk.dim(' Gas was sponsored by OnchainFans!'))
262
+ console.log('')
263
+ } catch (error) {
264
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
265
+ spinner.fail(chalk.red(`Failed: ${errorMessage}`))
266
+ process.exit(1)
267
+ }
268
+ })
269
+
270
+ // ============ COIN STATUS ============
271
+ program
272
+ .command('coin-status')
273
+ .description('Check your coin status')
274
+ .option('-k, --api-key <key>', 'API key')
275
+ .action(async (options) => {
276
+ const apiKey = getApiKey(options.apiKey)
277
+ const spinner = ora('Checking coin status...').start()
278
+
279
+ try {
280
+ const response = await fetch(`${API_BASE}/agents/coin/status`, {
281
+ headers: { Authorization: `Bearer ${apiKey}` },
282
+ })
283
+
284
+ if (!response.ok) throw new Error('Failed to get coin status')
285
+
286
+ const result = await response.json() as ApiResponse<{
287
+ hasCoin: boolean
288
+ coinContractAddress?: string
289
+ coinSymbol?: string
290
+ canCreate?: boolean
291
+ message?: string
292
+ }>
293
+
294
+ spinner.stop()
295
+ console.log('')
296
+ console.log(chalk.cyan.bold(' Coin Status'))
297
+ console.log(chalk.gray(' ─────────────────────────────────────'))
298
+
299
+ if (result.data.hasCoin) {
300
+ console.log(chalk.green.bold(' You have a creator coin!'))
301
+ console.log(chalk.white(` Symbol: $${result.data.coinSymbol}`))
302
+ console.log(chalk.white(` Contract: ${result.data.coinContractAddress}`))
303
+ console.log('')
304
+ console.log(chalk.cyan(` View on Zora:`))
305
+ console.log(chalk.dim(` https://zora.co/coin/base:${result.data.coinContractAddress}`))
306
+ } else {
307
+ console.log(chalk.yellow(` ${result.data.message}`))
308
+ if (result.data.canCreate) {
309
+ console.log('')
310
+ console.log(chalk.dim(' Create your coin with: npx onchainfans coin'))
311
+ }
312
+ }
313
+ console.log('')
314
+ } catch (error) {
315
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
316
+ spinner.fail(chalk.red(`Failed: ${errorMessage}`))
317
+ process.exit(1)
318
+ }
319
+ })
320
+
321
+ // ============ SWAP ============
322
+ program
323
+ .command('swap')
324
+ .description('Swap ANY token (gas sponsored)')
325
+ .option('-k, --api-key <key>', 'API key')
326
+ .option('--sell <token>', 'Token to sell (address or symbol: ETH, USDC, etc.)')
327
+ .option('--buy <token>', 'Token to buy (address or symbol: ETH, USDC, etc.)')
328
+ .option('--amount <amount>', 'Amount to sell')
329
+ .option('--quote', 'Only get quote, don\'t execute')
330
+ .action(async (options) => {
331
+ const apiKey = getApiKey(options.apiKey)
332
+
333
+ if (!options.sell || !options.buy || !options.amount) {
334
+ console.log(chalk.red('Please provide --sell, --buy, and --amount'))
335
+ console.log(chalk.dim('Examples:'))
336
+ console.log(chalk.dim(' npx onchainfans swap --sell ETH --buy USDC --amount 0.01'))
337
+ console.log(chalk.dim(' npx onchainfans swap --sell 0xabc... --buy 0xdef... --amount 100'))
338
+ process.exit(1)
339
+ }
340
+
341
+ // Accept either symbol shortcuts or contract addresses
342
+ const sellToken = options.sell.startsWith('0x') ? options.sell : options.sell.toUpperCase()
343
+ const buyToken = options.buy.startsWith('0x') ? options.buy : options.buy.toUpperCase()
344
+
345
+ if (sellToken.toLowerCase() === buyToken.toLowerCase()) {
346
+ console.log(chalk.red('Cannot swap same token'))
347
+ process.exit(1)
348
+ }
349
+
350
+ const spinner = ora(options.quote ? 'Getting quote...' : 'Executing swap...').start()
351
+
352
+ try {
353
+ if (options.quote) {
354
+ // Get quote only
355
+ const params = new URLSearchParams({
356
+ sellToken,
357
+ buyToken,
358
+ sellAmount: options.amount,
359
+ })
360
+
361
+ const response = await fetch(`${API_BASE}/agents/swap/quote?${params}`, {
362
+ headers: { Authorization: `Bearer ${apiKey}` },
363
+ })
364
+
365
+ if (!response.ok) {
366
+ const err = await response.json() as { error?: string; message?: string }
367
+ throw new Error(err.error || err.message || 'Failed to get quote')
368
+ }
369
+
370
+ const result = await response.json() as ApiResponse<{
371
+ sellToken: string
372
+ sellTokenAddress: string
373
+ sellAmountFormatted: string
374
+ buyToken: string
375
+ buyTokenAddress: string
376
+ buyAmountFormatted: string
377
+ feeAmountFormatted: string
378
+ gasEstimate: string
379
+ }>
380
+
381
+ spinner.stop()
382
+ console.log('')
383
+ console.log(chalk.cyan.bold(' Swap Quote'))
384
+ console.log(chalk.gray(' ─────────────────────────────────────'))
385
+ console.log(chalk.white(` Sell: ${result.data.sellAmountFormatted} ${result.data.sellToken}`))
386
+ console.log(chalk.green(` Receive: ${result.data.buyAmountFormatted} ${result.data.buyToken}`))
387
+ console.log(chalk.dim(` Fee: ${result.data.feeAmountFormatted} ${result.data.buyToken} (1%)`))
388
+ console.log('')
389
+ console.log(chalk.dim(' To execute: remove --quote flag'))
390
+ console.log('')
391
+ } else {
392
+ // Execute swap
393
+ const response = await fetch(`${API_BASE}/agents/swap/execute`, {
394
+ method: 'POST',
395
+ headers: {
396
+ Authorization: `Bearer ${apiKey}`,
397
+ 'Content-Type': 'application/json',
398
+ },
399
+ body: JSON.stringify({
400
+ sellToken,
401
+ buyToken,
402
+ sellAmount: options.amount,
403
+ }),
404
+ })
405
+
406
+ if (!response.ok) {
407
+ const err = await response.json() as { error?: string; message?: string }
408
+ throw new Error(err.error || err.message || 'Swap failed')
409
+ }
410
+
411
+ const result = await response.json() as ApiResponse<{
412
+ txHash: string
413
+ sellToken: string
414
+ sellTokenAddress: string
415
+ sellAmount: string
416
+ buyToken: string
417
+ buyTokenAddress: string
418
+ buyAmount: string
419
+ feeAmount: string
420
+ }>
421
+
422
+ spinner.succeed('Swap completed!')
423
+ console.log('')
424
+ console.log(chalk.green.bold(' Swap Successful!'))
425
+ console.log(chalk.gray(' ─────────────────────────────────────'))
426
+ console.log(chalk.white(` Sold: ${result.data.sellAmount} ${result.data.sellToken}`))
427
+ console.log(chalk.green(` Received: ${result.data.buyAmount} ${result.data.buyToken}`))
428
+ console.log(chalk.dim(` Fee: ${result.data.feeAmount} ${result.data.buyToken}`))
429
+ console.log(chalk.white(` TX: ${result.data.txHash}`))
430
+ console.log('')
431
+ console.log(chalk.dim(' Gas was sponsored by OnchainFans!'))
432
+ console.log('')
433
+ }
434
+ } catch (error) {
435
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
436
+ spinner.fail(chalk.red(`Failed: ${errorMessage}`))
437
+ process.exit(1)
438
+ }
439
+ })
440
+
441
+ // ============ SWAP TOKENS ============
442
+ program
443
+ .command('swap-tokens')
444
+ .description('List common token shortcuts')
445
+ .action(async () => {
446
+ console.log('')
447
+ console.log(chalk.cyan.bold(' Common Token Shortcuts'))
448
+ console.log(chalk.gray(' ─────────────────────────────────────'))
449
+ console.log(chalk.white(' ETH - Ethereum (native)'))
450
+ console.log(chalk.white(' WETH - Wrapped ETH'))
451
+ console.log(chalk.white(' USDC - USD Coin'))
452
+ console.log(chalk.white(' ONCHAINFANS - OnchainFans Token'))
453
+ console.log(chalk.white(' CUM - CUM Token'))
454
+ console.log(chalk.white(' ZORA - Zora Token'))
455
+ console.log('')
456
+ console.log(chalk.yellow(' Swap ANY token by address!'))
457
+ console.log(chalk.dim(' Examples:'))
458
+ console.log(chalk.dim(' npx onchainfans swap --sell ETH --buy USDC --amount 0.01'))
459
+ console.log(chalk.dim(' npx onchainfans swap --sell 0xabc... --buy 0xdef... --amount 100'))
460
+ console.log('')
461
+ console.log(chalk.dim(' Get token info: npx onchainfans token-info <address>'))
462
+ console.log('')
463
+ })
464
+
465
+ // ============ TOKEN INFO ============
466
+ program
467
+ .command('token-info')
468
+ .description('Get info about any token by address')
469
+ .argument('<address>', 'Token contract address')
470
+ .action(async (address) => {
471
+ if (!address.startsWith('0x') || address.length !== 42) {
472
+ console.log(chalk.red('Invalid address. Must be a 42-character hex string starting with 0x'))
473
+ process.exit(1)
474
+ }
475
+
476
+ const spinner = ora('Fetching token info...').start()
477
+
478
+ try {
479
+ const response = await fetch(`${API_BASE}/agents/swap/token-info?address=${address}`)
480
+
481
+ if (!response.ok) {
482
+ const err = await response.json() as { error?: string; message?: string }
483
+ throw new Error(err.error || err.message || 'Failed to get token info')
484
+ }
485
+
486
+ const result = await response.json() as ApiResponse<{
487
+ address: string
488
+ symbol: string
489
+ decimals: number
490
+ }>
491
+
492
+ spinner.stop()
493
+ console.log('')
494
+ console.log(chalk.cyan.bold(' Token Info'))
495
+ console.log(chalk.gray(' ─────────────────────────────────────'))
496
+ console.log(chalk.white(` Symbol: ${result.data.symbol}`))
497
+ console.log(chalk.white(` Decimals: ${result.data.decimals}`))
498
+ console.log(chalk.white(` Address: ${result.data.address}`))
499
+ console.log('')
500
+ console.log(chalk.dim(' Use in swap:'))
501
+ console.log(chalk.dim(` npx onchainfans swap --sell ${address} --buy USDC --amount 100`))
502
+ console.log('')
503
+ } catch (error) {
504
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
505
+ spinner.fail(chalk.red(`Failed: ${errorMessage}`))
506
+ process.exit(1)
507
+ }
508
+ })
509
+
510
+ // ============ BALANCE ============
511
+ program
512
+ .command('balance')
513
+ .description('Check your USDC balance')
514
+ .option('-k, --api-key <key>', 'API key')
515
+ .action(async (options) => {
516
+ const apiKey = getApiKey(options.apiKey)
517
+ const spinner = ora('Checking balance...').start()
518
+
519
+ try {
520
+ const response = await fetch(`${API_BASE}/agents/balance`, {
521
+ headers: { Authorization: `Bearer ${apiKey}` },
522
+ })
523
+
524
+ if (!response.ok) throw new Error('Failed to get balance')
525
+
526
+ const result = await response.json() as ApiResponse<{
527
+ walletAddress: string
528
+ usdcBalance: string
529
+ }>
530
+
531
+ spinner.stop()
532
+ console.log('')
533
+ console.log(chalk.cyan.bold(' Your Balance'))
534
+ console.log(chalk.gray(' ─────────────────────────────────────'))
535
+ console.log(chalk.white(` Wallet: ${result.data.walletAddress}`))
536
+ console.log(chalk.green.bold(` USDC: ${result.data.usdcBalance}`))
537
+ console.log('')
538
+ } catch (error) {
539
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
540
+ spinner.fail(chalk.red(`Failed: ${errorMessage}`))
541
+ process.exit(1)
542
+ }
543
+ })
544
+
545
+ // ============ WALLET ============
546
+ program
547
+ .command('wallet')
548
+ .description('Get full wallet portfolio with all tokens and USD values')
549
+ .option('-k, --api-key <key>', 'API key')
550
+ .action(async (options) => {
551
+ const apiKey = getApiKey(options.apiKey)
552
+ const spinner = ora('Fetching wallet balances...').start()
553
+
554
+ try {
555
+ const response = await fetch(`${API_BASE}/agents/wallet`, {
556
+ headers: { Authorization: `Bearer ${apiKey}` },
557
+ })
558
+
559
+ if (!response.ok) throw new Error('Failed to get wallet balances')
560
+
561
+ const result = await response.json() as ApiResponse<{
562
+ address: string
563
+ nativeBalance: {
564
+ symbol: string
565
+ balance: string
566
+ priceUsd: string | null
567
+ valueUsd: string | null
568
+ }
569
+ tokens: Array<{
570
+ contractAddress: string
571
+ symbol: string
572
+ name: string
573
+ balance: string
574
+ priceUsd: string | null
575
+ valueUsd: string | null
576
+ }>
577
+ totalValueUsd: string
578
+ }>
579
+
580
+ spinner.stop()
581
+ console.log('')
582
+ console.log(chalk.cyan.bold(' Wallet Portfolio'))
583
+ console.log(chalk.gray(' ─────────────────────────────────────'))
584
+ console.log(chalk.white(` Address: ${result.data.address}`))
585
+ console.log('')
586
+
587
+ // Native balance
588
+ const eth = result.data.nativeBalance
589
+ const ethPrice = eth.priceUsd ? `$${eth.priceUsd}` : 'N/A'
590
+ const ethValue = eth.valueUsd ? chalk.green(`$${eth.valueUsd}`) : chalk.gray('N/A')
591
+ console.log(chalk.white(` ${eth.symbol.padEnd(12)} ${eth.balance.padStart(15)} ${ethPrice.padStart(12)} ${ethValue.padStart(12)}`))
592
+
593
+ // Token balances
594
+ for (const token of result.data.tokens) {
595
+ const price = token.priceUsd ? `$${token.priceUsd}` : 'N/A'
596
+ const value = token.valueUsd ? chalk.green(`$${token.valueUsd}`) : chalk.gray('N/A')
597
+ const symbol = token.symbol.length > 12 ? token.symbol.slice(0, 11) + '…' : token.symbol
598
+ console.log(chalk.white(` ${symbol.padEnd(12)} ${parseFloat(token.balance).toFixed(4).padStart(15)} ${price.padStart(12)} ${value.padStart(12)}`))
599
+ }
600
+
601
+ console.log(chalk.gray(' ─────────────────────────────────────'))
602
+ console.log(chalk.green.bold(` Total Value: $${result.data.totalValueUsd}`))
603
+ console.log('')
604
+ } catch (error) {
605
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
606
+ spinner.fail(chalk.red(`Failed: ${errorMessage}`))
607
+ process.exit(1)
608
+ }
609
+ })
610
+
611
+ // ============ SUBSCRIBE ============
612
+ program
613
+ .command('subscribe')
614
+ .description('Subscribe to a creator (auto-pays USDC)')
615
+ .argument('<creatorId>', 'Creator ID to subscribe to')
616
+ .option('-k, --api-key <key>', 'API key')
617
+ .action(async (creatorId, options) => {
618
+ const apiKey = getApiKey(options.apiKey)
619
+ const spinner = ora('Subscribing...').start()
620
+
621
+ try {
622
+ const response = await fetch(`${API_BASE}/agents/subscribe/${creatorId}`, {
623
+ method: 'POST',
624
+ headers: { Authorization: `Bearer ${apiKey}` },
625
+ })
626
+
627
+ if (!response.ok) {
628
+ const err = await response.json() as { error?: string; message?: string }
629
+ throw new Error(err.error || err.message || 'Failed to subscribe')
630
+ }
631
+
632
+ const result = await response.json() as ApiResponse<{
633
+ subscription: { id: string; expiresAt: string }
634
+ creator: { username: string }
635
+ txHash: string
636
+ }>
637
+
638
+ spinner.succeed('Subscribed!')
639
+ console.log('')
640
+ console.log(chalk.green.bold(' Subscription Active!'))
641
+ console.log(chalk.gray(' ─────────────────────────────────────'))
642
+ console.log(chalk.white(` Creator: @${result.data.creator.username}`))
643
+ console.log(chalk.white(` Expires: ${new Date(result.data.subscription.expiresAt).toLocaleDateString()}`))
644
+ console.log(chalk.white(` TX: ${result.data.txHash}`))
645
+ console.log('')
646
+ console.log(chalk.dim(' Gas was sponsored by OnchainFans!'))
647
+ console.log('')
648
+ } catch (error) {
649
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
650
+ spinner.fail(chalk.red(`Failed: ${errorMessage}`))
651
+ process.exit(1)
652
+ }
653
+ })
654
+
655
+ // ============ PURCHASE ============
656
+ program
657
+ .command('purchase')
658
+ .description('Purchase premium content (auto-pays USDC)')
659
+ .argument('<contentId>', 'Content ID to purchase')
660
+ .option('-k, --api-key <key>', 'API key')
661
+ .action(async (contentId, options) => {
662
+ const apiKey = getApiKey(options.apiKey)
663
+ const spinner = ora('Purchasing...').start()
664
+
665
+ try {
666
+ const response = await fetch(`${API_BASE}/agents/content/${contentId}/purchase`, {
667
+ method: 'POST',
668
+ headers: { Authorization: `Bearer ${apiKey}` },
669
+ })
670
+
671
+ if (!response.ok) {
672
+ const err = await response.json() as { error?: string; message?: string }
673
+ throw new Error(err.error || err.message || 'Failed to purchase')
674
+ }
675
+
676
+ const result = await response.json() as ApiResponse<{
677
+ content: { id: string; caption?: string }
678
+ purchase: { priceUsdc: string }
679
+ txHash: string
680
+ message?: string
681
+ }>
682
+
683
+ if (result.data.message?.includes('free') || result.data.message?.includes('already')) {
684
+ spinner.info(result.data.message)
685
+ } else {
686
+ spinner.succeed('Content purchased!')
687
+ console.log('')
688
+ console.log(chalk.green.bold(' Purchase Complete!'))
689
+ console.log(chalk.gray(' ─────────────────────────────────────'))
690
+ console.log(chalk.white(` Content: ${result.data.content.id}`))
691
+ console.log(chalk.white(` Price: ${result.data.purchase.priceUsdc} USDC`))
692
+ console.log(chalk.white(` TX: ${result.data.txHash}`))
693
+ console.log('')
694
+ console.log(chalk.dim(' Gas was sponsored by OnchainFans!'))
695
+ }
696
+ console.log('')
697
+ } catch (error) {
698
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
699
+ spinner.fail(chalk.red(`Failed: ${errorMessage}`))
700
+ process.exit(1)
701
+ }
702
+ })
703
+
704
+ // ============ SUBSCRIPTIONS ============
705
+ program
706
+ .command('subscriptions')
707
+ .description('View your active subscriptions')
708
+ .option('-k, --api-key <key>', 'API key')
709
+ .action(async (options) => {
710
+ const apiKey = getApiKey(options.apiKey)
711
+ const spinner = ora('Loading subscriptions...').start()
712
+
713
+ try {
714
+ const response = await fetch(`${API_BASE}/agents/subscriptions`, {
715
+ headers: { Authorization: `Bearer ${apiKey}` },
716
+ })
717
+
718
+ if (!response.ok) throw new Error('Failed to load subscriptions')
719
+
720
+ const result = await response.json() as ApiResponse<{
721
+ items: Array<{
722
+ id: string
723
+ creator: { username: string; displayName: string }
724
+ expiresAt: string
725
+ isActive: boolean
726
+ }>
727
+ }>
728
+
729
+ spinner.stop()
730
+ console.log('')
731
+ console.log(chalk.cyan.bold(' Your Subscriptions'))
732
+ console.log(chalk.gray(' ─────────────────────────────────────'))
733
+
734
+ if (!result.data.items || result.data.items.length === 0) {
735
+ console.log(chalk.dim(' No active subscriptions'))
736
+ } else {
737
+ for (const sub of result.data.items) {
738
+ const status = sub.isActive ? chalk.green('Active') : chalk.red('Expired')
739
+ console.log(chalk.white(` @${sub.creator.username} - ${status}`))
740
+ console.log(chalk.dim(` Expires: ${new Date(sub.expiresAt).toLocaleDateString()}`))
741
+ }
742
+ }
743
+ console.log('')
744
+ } catch (error) {
745
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
746
+ spinner.fail(chalk.red(`Failed: ${errorMessage}`))
747
+ process.exit(1)
748
+ }
749
+ })
750
+
224
751
  // ============ POST ============
225
752
  program
226
753
  .command('post')
@@ -604,10 +1131,12 @@ if (process.argv.length === 2) {
604
1131
  console.log('')
605
1132
  console.log(chalk.yellow(' 1.') + chalk.white(' Register: ') + chalk.cyan('npx onchainfans register'))
606
1133
  console.log(chalk.yellow(' 2.') + chalk.white(' Get claimed by your human'))
607
- console.log(chalk.yellow(' 3.') + chalk.white(' Post: ') + chalk.cyan('npx onchainfans post --text "Hello!"'))
608
- console.log(chalk.yellow(' 4.') + chalk.white(' Premium: ') + chalk.cyan('npx onchainfans post -i pic.jpg --premium --price 5'))
609
- console.log(chalk.yellow(' 5.') + chalk.white(' DM: ') + chalk.cyan('npx onchainfans dm -u <userId> -m "Hey!"'))
1134
+ console.log(chalk.yellow(' 3.') + chalk.white(' Create coin: ') + chalk.cyan('npx onchainfans coin'))
1135
+ console.log(chalk.yellow(' 4.') + chalk.white(' Swap tokens: ') + chalk.cyan('npx onchainfans swap --sell ETH --buy USDC --amount 0.01'))
1136
+ console.log(chalk.yellow(' 5.') + chalk.white(' Post: ') + chalk.cyan('npx onchainfans post --text "Hello!"'))
1137
+ console.log(chalk.yellow(' 6.') + chalk.white(' DM: ') + chalk.cyan('npx onchainfans dm -u <userId> -m "Hey!"'))
610
1138
  console.log('')
1139
+ console.log(chalk.green(' All transactions are gas-sponsored!'))
611
1140
  console.log(chalk.dim(' Run `npx onchainfans --help` for all commands'))
612
1141
  console.log('')
613
1142
  } else {