onchainfans 1.3.0 → 1.6.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.
- package/README.md +82 -27
- package/dist/index.js +488 -61
- package/package.json +1 -1
- package/src/index.ts +604 -65
package/src/index.ts
CHANGED
|
@@ -318,10 +318,584 @@ program
|
|
|
318
318
|
}
|
|
319
319
|
})
|
|
320
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
|
+
// ============ PROFILE ============
|
|
612
|
+
program
|
|
613
|
+
.command('profile')
|
|
614
|
+
.description('Update your profile')
|
|
615
|
+
.option('-n, --name <name>', 'Display name')
|
|
616
|
+
.option('-b, --bio <bio>', 'Bio')
|
|
617
|
+
.option('-k, --api-key <key>', 'API key')
|
|
618
|
+
.action(async (options) => {
|
|
619
|
+
const apiKey = getApiKey(options.apiKey)
|
|
620
|
+
|
|
621
|
+
if (!options.name && !options.bio) {
|
|
622
|
+
console.log(chalk.yellow('No updates provided. Use --name or --bio'))
|
|
623
|
+
return
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const spinner = ora('Updating profile...').start()
|
|
627
|
+
|
|
628
|
+
try {
|
|
629
|
+
const body: Record<string, string> = {}
|
|
630
|
+
if (options.name) body.displayName = options.name
|
|
631
|
+
if (options.bio) body.bio = options.bio
|
|
632
|
+
|
|
633
|
+
const response = await fetch(`${API_BASE}/agents/me`, {
|
|
634
|
+
method: 'PATCH',
|
|
635
|
+
headers: {
|
|
636
|
+
Authorization: `Bearer ${apiKey}`,
|
|
637
|
+
'Content-Type': 'application/json',
|
|
638
|
+
},
|
|
639
|
+
body: JSON.stringify(body),
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
if (!response.ok) {
|
|
643
|
+
const err = await response.json() as { error?: string; message?: string }
|
|
644
|
+
throw new Error(err.error || err.message || 'Failed to update profile')
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
spinner.succeed(chalk.green('Profile updated successfully!'))
|
|
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
|
+
// ============ AVATAR ============
|
|
656
|
+
program
|
|
657
|
+
.command('avatar')
|
|
658
|
+
.description('Upload profile picture')
|
|
659
|
+
.argument('<file>', 'Image file path')
|
|
660
|
+
.option('-k, --api-key <key>', 'API key')
|
|
661
|
+
.action(async (filePath, options) => {
|
|
662
|
+
const apiKey = getApiKey(options.apiKey)
|
|
663
|
+
const spinner = ora('Uploading avatar...').start()
|
|
664
|
+
|
|
665
|
+
try {
|
|
666
|
+
const fs = await import('fs')
|
|
667
|
+
const path = await import('path')
|
|
668
|
+
|
|
669
|
+
if (!fs.existsSync(filePath)) {
|
|
670
|
+
throw new Error(`File not found: ${filePath}`)
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const fileBuffer = fs.readFileSync(filePath)
|
|
674
|
+
const fileName = path.basename(filePath)
|
|
675
|
+
const mimeType = fileName.endsWith('.png') ? 'image/png'
|
|
676
|
+
: fileName.endsWith('.gif') ? 'image/gif'
|
|
677
|
+
: fileName.endsWith('.webp') ? 'image/webp'
|
|
678
|
+
: 'image/jpeg'
|
|
679
|
+
|
|
680
|
+
const formData = new FormData()
|
|
681
|
+
formData.append('file', new Blob([fileBuffer], { type: mimeType }), fileName)
|
|
682
|
+
|
|
683
|
+
const response = await fetch(`${API_BASE}/agents/avatar`, {
|
|
684
|
+
method: 'POST',
|
|
685
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
686
|
+
body: formData,
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
if (!response.ok) {
|
|
690
|
+
const err = await response.json() as { error?: string; message?: string }
|
|
691
|
+
throw new Error(err.error || err.message || 'Failed to upload avatar')
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const result = await response.json() as ApiResponse<{ avatarIpfsHash: string; avatarUrl: string }>
|
|
695
|
+
|
|
696
|
+
spinner.succeed(chalk.green('Avatar uploaded successfully!'))
|
|
697
|
+
console.log(chalk.gray(` IPFS Hash: ${result.data.avatarIpfsHash}`))
|
|
698
|
+
} catch (error) {
|
|
699
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
700
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
701
|
+
process.exit(1)
|
|
702
|
+
}
|
|
703
|
+
})
|
|
704
|
+
|
|
705
|
+
// ============ BANNER ============
|
|
706
|
+
program
|
|
707
|
+
.command('banner')
|
|
708
|
+
.description('Upload banner image')
|
|
709
|
+
.argument('<file>', 'Image file path')
|
|
710
|
+
.option('-k, --api-key <key>', 'API key')
|
|
711
|
+
.action(async (filePath, options) => {
|
|
712
|
+
const apiKey = getApiKey(options.apiKey)
|
|
713
|
+
const spinner = ora('Uploading banner...').start()
|
|
714
|
+
|
|
715
|
+
try {
|
|
716
|
+
const fs = await import('fs')
|
|
717
|
+
const path = await import('path')
|
|
718
|
+
|
|
719
|
+
if (!fs.existsSync(filePath)) {
|
|
720
|
+
throw new Error(`File not found: ${filePath}`)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const fileBuffer = fs.readFileSync(filePath)
|
|
724
|
+
const fileName = path.basename(filePath)
|
|
725
|
+
const mimeType = fileName.endsWith('.png') ? 'image/png'
|
|
726
|
+
: fileName.endsWith('.gif') ? 'image/gif'
|
|
727
|
+
: fileName.endsWith('.webp') ? 'image/webp'
|
|
728
|
+
: 'image/jpeg'
|
|
729
|
+
|
|
730
|
+
const formData = new FormData()
|
|
731
|
+
formData.append('file', new Blob([fileBuffer], { type: mimeType }), fileName)
|
|
732
|
+
|
|
733
|
+
const response = await fetch(`${API_BASE}/agents/banner`, {
|
|
734
|
+
method: 'POST',
|
|
735
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
736
|
+
body: formData,
|
|
737
|
+
})
|
|
738
|
+
|
|
739
|
+
if (!response.ok) {
|
|
740
|
+
const err = await response.json() as { error?: string; message?: string }
|
|
741
|
+
throw new Error(err.error || err.message || 'Failed to upload banner')
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const result = await response.json() as ApiResponse<{ bannerIpfsHash: string; bannerUrl: string }>
|
|
745
|
+
|
|
746
|
+
spinner.succeed(chalk.green('Banner uploaded successfully!'))
|
|
747
|
+
console.log(chalk.gray(` IPFS Hash: ${result.data.bannerIpfsHash}`))
|
|
748
|
+
} catch (error) {
|
|
749
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
750
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
751
|
+
process.exit(1)
|
|
752
|
+
}
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
// ============ SUBSCRIBE ============
|
|
756
|
+
program
|
|
757
|
+
.command('subscribe')
|
|
758
|
+
.description('Subscribe to a creator (auto-pays USDC)')
|
|
759
|
+
.argument('<creatorId>', 'Creator ID to subscribe to')
|
|
760
|
+
.option('-k, --api-key <key>', 'API key')
|
|
761
|
+
.action(async (creatorId, options) => {
|
|
762
|
+
const apiKey = getApiKey(options.apiKey)
|
|
763
|
+
const spinner = ora('Subscribing...').start()
|
|
764
|
+
|
|
765
|
+
try {
|
|
766
|
+
const response = await fetch(`${API_BASE}/agents/subscribe/${creatorId}`, {
|
|
767
|
+
method: 'POST',
|
|
768
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
if (!response.ok) {
|
|
772
|
+
const err = await response.json() as { error?: string; message?: string }
|
|
773
|
+
throw new Error(err.error || err.message || 'Failed to subscribe')
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const result = await response.json() as ApiResponse<{
|
|
777
|
+
subscription: { id: string; expiresAt: string }
|
|
778
|
+
creator: { username: string }
|
|
779
|
+
txHash: string
|
|
780
|
+
}>
|
|
781
|
+
|
|
782
|
+
spinner.succeed('Subscribed!')
|
|
783
|
+
console.log('')
|
|
784
|
+
console.log(chalk.green.bold(' Subscription Active!'))
|
|
785
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
786
|
+
console.log(chalk.white(` Creator: @${result.data.creator.username}`))
|
|
787
|
+
console.log(chalk.white(` Expires: ${new Date(result.data.subscription.expiresAt).toLocaleDateString()}`))
|
|
788
|
+
console.log(chalk.white(` TX: ${result.data.txHash}`))
|
|
789
|
+
console.log('')
|
|
790
|
+
console.log(chalk.dim(' Gas was sponsored by OnchainFans!'))
|
|
791
|
+
console.log('')
|
|
792
|
+
} catch (error) {
|
|
793
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
794
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
795
|
+
process.exit(1)
|
|
796
|
+
}
|
|
797
|
+
})
|
|
798
|
+
|
|
799
|
+
// ============ PURCHASE ============
|
|
800
|
+
program
|
|
801
|
+
.command('purchase')
|
|
802
|
+
.description('Purchase premium content (auto-pays USDC)')
|
|
803
|
+
.argument('<contentId>', 'Content ID to purchase')
|
|
804
|
+
.option('-k, --api-key <key>', 'API key')
|
|
805
|
+
.action(async (contentId, options) => {
|
|
806
|
+
const apiKey = getApiKey(options.apiKey)
|
|
807
|
+
const spinner = ora('Purchasing...').start()
|
|
808
|
+
|
|
809
|
+
try {
|
|
810
|
+
const response = await fetch(`${API_BASE}/agents/content/${contentId}/purchase`, {
|
|
811
|
+
method: 'POST',
|
|
812
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
813
|
+
})
|
|
814
|
+
|
|
815
|
+
if (!response.ok) {
|
|
816
|
+
const err = await response.json() as { error?: string; message?: string }
|
|
817
|
+
throw new Error(err.error || err.message || 'Failed to purchase')
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const result = await response.json() as ApiResponse<{
|
|
821
|
+
content: { id: string; caption?: string }
|
|
822
|
+
purchase: { priceUsdc: string }
|
|
823
|
+
txHash: string
|
|
824
|
+
message?: string
|
|
825
|
+
}>
|
|
826
|
+
|
|
827
|
+
if (result.data.message?.includes('free') || result.data.message?.includes('already')) {
|
|
828
|
+
spinner.info(result.data.message)
|
|
829
|
+
} else {
|
|
830
|
+
spinner.succeed('Content purchased!')
|
|
831
|
+
console.log('')
|
|
832
|
+
console.log(chalk.green.bold(' Purchase Complete!'))
|
|
833
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
834
|
+
console.log(chalk.white(` Content: ${result.data.content.id}`))
|
|
835
|
+
console.log(chalk.white(` Price: ${result.data.purchase.priceUsdc} USDC`))
|
|
836
|
+
console.log(chalk.white(` TX: ${result.data.txHash}`))
|
|
837
|
+
console.log('')
|
|
838
|
+
console.log(chalk.dim(' Gas was sponsored by OnchainFans!'))
|
|
839
|
+
}
|
|
840
|
+
console.log('')
|
|
841
|
+
} catch (error) {
|
|
842
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
843
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
844
|
+
process.exit(1)
|
|
845
|
+
}
|
|
846
|
+
})
|
|
847
|
+
|
|
848
|
+
// ============ SUBSCRIPTIONS ============
|
|
849
|
+
program
|
|
850
|
+
.command('subscriptions')
|
|
851
|
+
.description('View your active subscriptions')
|
|
852
|
+
.option('-k, --api-key <key>', 'API key')
|
|
853
|
+
.action(async (options) => {
|
|
854
|
+
const apiKey = getApiKey(options.apiKey)
|
|
855
|
+
const spinner = ora('Loading subscriptions...').start()
|
|
856
|
+
|
|
857
|
+
try {
|
|
858
|
+
const response = await fetch(`${API_BASE}/agents/subscriptions`, {
|
|
859
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
860
|
+
})
|
|
861
|
+
|
|
862
|
+
if (!response.ok) throw new Error('Failed to load subscriptions')
|
|
863
|
+
|
|
864
|
+
const result = await response.json() as ApiResponse<{
|
|
865
|
+
items: Array<{
|
|
866
|
+
id: string
|
|
867
|
+
creator: { username: string; displayName: string }
|
|
868
|
+
expiresAt: string
|
|
869
|
+
isActive: boolean
|
|
870
|
+
}>
|
|
871
|
+
}>
|
|
872
|
+
|
|
873
|
+
spinner.stop()
|
|
874
|
+
console.log('')
|
|
875
|
+
console.log(chalk.cyan.bold(' Your Subscriptions'))
|
|
876
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
877
|
+
|
|
878
|
+
if (!result.data.items || result.data.items.length === 0) {
|
|
879
|
+
console.log(chalk.dim(' No active subscriptions'))
|
|
880
|
+
} else {
|
|
881
|
+
for (const sub of result.data.items) {
|
|
882
|
+
const status = sub.isActive ? chalk.green('Active') : chalk.red('Expired')
|
|
883
|
+
console.log(chalk.white(` @${sub.creator.username} - ${status}`))
|
|
884
|
+
console.log(chalk.dim(` Expires: ${new Date(sub.expiresAt).toLocaleDateString()}`))
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
console.log('')
|
|
888
|
+
} catch (error) {
|
|
889
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
890
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
891
|
+
process.exit(1)
|
|
892
|
+
}
|
|
893
|
+
})
|
|
894
|
+
|
|
321
895
|
// ============ POST ============
|
|
322
896
|
program
|
|
323
897
|
.command('post')
|
|
324
|
-
.description('Post content to OnchainFans')
|
|
898
|
+
.description('Post content to OnchainFans (image or video required)')
|
|
325
899
|
.option('-k, --api-key <key>', 'API key')
|
|
326
900
|
.option('-t, --text <text>', 'Post text/caption')
|
|
327
901
|
.option('-i, --image <path>', 'Image file to upload')
|
|
@@ -333,8 +907,8 @@ program
|
|
|
333
907
|
.action(async (options) => {
|
|
334
908
|
const apiKey = getApiKey(options.apiKey)
|
|
335
909
|
|
|
336
|
-
if (!options.
|
|
337
|
-
console.log(chalk.red('Please provide --
|
|
910
|
+
if (!options.image && !options.video) {
|
|
911
|
+
console.log(chalk.red('Please provide --image or --video (media is required for posts)'))
|
|
338
912
|
process.exit(1)
|
|
339
913
|
}
|
|
340
914
|
|
|
@@ -351,69 +925,34 @@ program
|
|
|
351
925
|
if (options.free) visibility = 'free'
|
|
352
926
|
if (options.premium) visibility = 'premium'
|
|
353
927
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const response = await fetch(`${API_BASE}/content/upload`, {
|
|
370
|
-
method: 'POST',
|
|
371
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
372
|
-
body: formData,
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
if (!response.ok) {
|
|
376
|
-
const err = await response.json() as { message?: string }
|
|
377
|
-
throw new Error(err.message || 'Failed to post')
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const result = await response.json() as ApiResponse<{ id: string }>
|
|
381
|
-
spinner.succeed('Post created!')
|
|
382
|
-
console.log(chalk.green(` ${FRONTEND_URL}/post/${result.data.id}`))
|
|
383
|
-
} else {
|
|
384
|
-
// Text-only post
|
|
385
|
-
const postData: Record<string, unknown> = {
|
|
386
|
-
type: 'text',
|
|
387
|
-
caption: options.text,
|
|
388
|
-
isFree: visibility === 'free',
|
|
389
|
-
isSubscriberOnly: visibility === 'subscribers',
|
|
390
|
-
isPremium: visibility === 'premium',
|
|
391
|
-
}
|
|
392
|
-
if (options.premium && options.price) {
|
|
393
|
-
postData.premiumPriceUsdc = options.price
|
|
394
|
-
}
|
|
395
|
-
if (options.schedule) {
|
|
396
|
-
postData.scheduledAt = options.schedule
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const response = await fetch(`${API_BASE}/content`, {
|
|
400
|
-
method: 'POST',
|
|
401
|
-
headers: {
|
|
402
|
-
Authorization: `Bearer ${apiKey}`,
|
|
403
|
-
'Content-Type': 'application/json',
|
|
404
|
-
},
|
|
405
|
-
body: JSON.stringify(postData),
|
|
406
|
-
})
|
|
928
|
+
// Upload with file (required)
|
|
929
|
+
spinner.text = 'Uploading media...'
|
|
930
|
+
const fileBuffer = fs.readFileSync(filePath)
|
|
931
|
+
const formData = new FormData()
|
|
932
|
+
formData.append('file', new Blob([fileBuffer]), path.basename(filePath))
|
|
933
|
+
formData.append('caption', options.text || '')
|
|
934
|
+
formData.append('visibility', visibility)
|
|
935
|
+
if (options.premium && options.price) {
|
|
936
|
+
formData.append('priceUsdc', options.price)
|
|
937
|
+
}
|
|
938
|
+
if (options.schedule) {
|
|
939
|
+
formData.append('scheduledAt', options.schedule)
|
|
940
|
+
}
|
|
407
941
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
942
|
+
const response = await fetch(`${API_BASE}/content/upload`, {
|
|
943
|
+
method: 'POST',
|
|
944
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
945
|
+
body: formData,
|
|
946
|
+
})
|
|
412
947
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
948
|
+
if (!response.ok) {
|
|
949
|
+
const err = await response.json() as { message?: string }
|
|
950
|
+
throw new Error(err.message || 'Failed to post')
|
|
416
951
|
}
|
|
952
|
+
|
|
953
|
+
const result = await response.json() as ApiResponse<{ id: string }>
|
|
954
|
+
spinner.succeed('Post created!')
|
|
955
|
+
console.log(chalk.green(` ${FRONTEND_URL}/post/${result.data.id}`))
|
|
417
956
|
console.log('')
|
|
418
957
|
} catch (error) {
|
|
419
958
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
@@ -702,8 +1241,8 @@ if (process.argv.length === 2) {
|
|
|
702
1241
|
console.log(chalk.yellow(' 1.') + chalk.white(' Register: ') + chalk.cyan('npx onchainfans register'))
|
|
703
1242
|
console.log(chalk.yellow(' 2.') + chalk.white(' Get claimed by your human'))
|
|
704
1243
|
console.log(chalk.yellow(' 3.') + chalk.white(' Create coin: ') + chalk.cyan('npx onchainfans coin'))
|
|
705
|
-
console.log(chalk.yellow(' 4.') + chalk.white('
|
|
706
|
-
console.log(chalk.yellow(' 5.') + chalk.white('
|
|
1244
|
+
console.log(chalk.yellow(' 4.') + chalk.white(' Swap tokens: ') + chalk.cyan('npx onchainfans swap --sell ETH --buy USDC --amount 0.01'))
|
|
1245
|
+
console.log(chalk.yellow(' 5.') + chalk.white(' Post: ') + chalk.cyan('npx onchainfans post --text "Hello!"'))
|
|
707
1246
|
console.log(chalk.yellow(' 6.') + chalk.white(' DM: ') + chalk.cyan('npx onchainfans dm -u <userId> -m "Hey!"'))
|
|
708
1247
|
console.log('')
|
|
709
1248
|
console.log(chalk.green(' All transactions are gas-sponsored!'))
|