@jellylegsai/aether-cli 1.8.0 → 1.9.1
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/commands/index.js +8 -0
- package/commands/nft.js +1180 -857
- package/commands/tx.js +487 -0
- package/index.js +7 -5
- package/package.json +4 -4
- package/sdk/index.js +1748 -1639
- package/sdk/package.json +1 -1
- package/aether-cli-1.0.0.tgz +0 -0
- package/aether-cli-1.8.0.tgz +0 -0
- package/aether-hub-1.0.5.tgz +0 -0
- package/aether-hub-1.1.8.tgz +0 -0
- package/aether-hub-1.2.1.tgz +0 -0
package/commands/nft.js
CHANGED
|
@@ -1,857 +1,1180 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* aether-cli nft
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* aether nft create
|
|
10
|
-
* aether nft
|
|
11
|
-
* aether nft transfer
|
|
12
|
-
* aether nft
|
|
13
|
-
* aether nft
|
|
14
|
-
* aether nft
|
|
15
|
-
*
|
|
16
|
-
* SDK
|
|
17
|
-
* - client.
|
|
18
|
-
* - client.
|
|
19
|
-
* - client.
|
|
20
|
-
* -
|
|
21
|
-
* -
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// ============================================================================
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// ============================================================================
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// ============================================================================
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// ============================================================================
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
console.log(` ${C.
|
|
239
|
-
rl.close();
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
if (
|
|
246
|
-
console.log(
|
|
247
|
-
console.log(` ${C.dim}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (
|
|
288
|
-
console.log(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
console.log(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
console.log(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}))
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
console.log();
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
console.log();
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
console.log(
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
if (
|
|
644
|
-
console.log(`\n ${C.red}✗
|
|
645
|
-
rl.close();
|
|
646
|
-
return;
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
console.log(` ${C.
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
${C.
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
${C.
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
${C.
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
//
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* aether-cli nft
|
|
4
|
+
*
|
|
5
|
+
* NFT management commands for the Aether blockchain.
|
|
6
|
+
* Create, mint, transfer, and manage NFTs with real RPC calls.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* aether nft create --metadata <url> [--royalties <bps>] [--json]
|
|
10
|
+
* aether nft mint --nft <id> --amount <n> [--to <addr>] [--json]
|
|
11
|
+
* aether nft transfer --nft <id> --to <addr> [--json]
|
|
12
|
+
* aether nft update --nft <id> --metadata <url> [--json]
|
|
13
|
+
* aether nft list --address <addr> [--json]
|
|
14
|
+
* aether nft info --nft <id> [--json]
|
|
15
|
+
*
|
|
16
|
+
* SDK wired to:
|
|
17
|
+
* - client.sendTransaction(tx) → POST /v1/transaction
|
|
18
|
+
* - client.getAccountInfo(addr) → GET /v1/account/<addr>
|
|
19
|
+
* - client.getNFT(nftId) → GET /v1/nft/<id>
|
|
20
|
+
* - client.getNFTHoldings(address) → GET /v1/nft-holdings/<addr>
|
|
21
|
+
* - client.getSlot() → GET /v1/slot
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const os = require('os');
|
|
27
|
+
const readline = require('readline');
|
|
28
|
+
const nacl = require('tweetnacl');
|
|
29
|
+
const bs58 = require('bs58').default;
|
|
30
|
+
const bip39 = require('bip39');
|
|
31
|
+
|
|
32
|
+
// Import SDK for ALL blockchain RPC calls
|
|
33
|
+
const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
|
|
34
|
+
const aether = require(sdkPath);
|
|
35
|
+
|
|
36
|
+
// ANSI colours
|
|
37
|
+
const C = {
|
|
38
|
+
reset: '\x1b[0m',
|
|
39
|
+
bright: '\x1b[1m',
|
|
40
|
+
dim: '\x1b[2m',
|
|
41
|
+
red: '\x1b[31m',
|
|
42
|
+
green: '\x1b[32m',
|
|
43
|
+
yellow: '\x1b[33m',
|
|
44
|
+
cyan: '\x1b[36m',
|
|
45
|
+
magenta: '\x1b[35m',
|
|
46
|
+
blue: '\x1b[34m',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const CLI_VERSION = '1.0.0';
|
|
50
|
+
const DERIVATION_PATH = "m/44'/7777777'/0'/0'";
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// SDK Setup
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
function getDefaultRpc() {
|
|
57
|
+
return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createClient(rpcUrl) {
|
|
61
|
+
return new aether.AetherClient({ rpcUrl });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Config & Wallet
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
function getAetherDir() {
|
|
69
|
+
return path.join(os.homedir(), '.aether');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getConfigPath() {
|
|
73
|
+
return path.join(getAetherDir(), 'config.json');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function loadConfig() {
|
|
77
|
+
if (!fs.existsSync(getConfigPath())) {
|
|
78
|
+
return { defaultWallet: null };
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(fs.readFileSync(getConfigPath(), 'utf8'));
|
|
82
|
+
} catch {
|
|
83
|
+
return { defaultWallet: null };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadWallet(address) {
|
|
88
|
+
const fp = path.join(getAetherDir(), 'wallets', `${address}.json`);
|
|
89
|
+
if (!fs.existsSync(fp)) return null;
|
|
90
|
+
try {
|
|
91
|
+
return JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// Crypto Helpers
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
101
|
+
function deriveKeypair(mnemonic) {
|
|
102
|
+
if (!bip39.validateMnemonic(mnemonic)) {
|
|
103
|
+
throw new Error('Invalid mnemonic phrase');
|
|
104
|
+
}
|
|
105
|
+
const seedBuffer = bip39.mnemonicToSeedSync(mnemonic, '');
|
|
106
|
+
const seed32 = seedBuffer.slice(0, 32);
|
|
107
|
+
const keyPair = nacl.sign.keyPair.fromSeed(seed32);
|
|
108
|
+
return {
|
|
109
|
+
publicKey: Buffer.from(keyPair.publicKey),
|
|
110
|
+
secretKey: Buffer.from(keyPair.secretKey),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function formatAddress(publicKey) {
|
|
115
|
+
return 'ATH' + bs58.encode(publicKey);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function signTransaction(tx, secretKey) {
|
|
119
|
+
const txBytes = Buffer.from(JSON.stringify(tx));
|
|
120
|
+
const sig = nacl.sign.detached(txBytes, secretKey);
|
|
121
|
+
return bs58.encode(sig);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================================================
|
|
125
|
+
// Format Helpers
|
|
126
|
+
// ============================================================================
|
|
127
|
+
|
|
128
|
+
function shortAddress(addr) {
|
|
129
|
+
if (!addr || addr.length < 20) return addr || 'unknown';
|
|
130
|
+
return addr.slice(0, 8) + '...' + addr.slice(-8);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function formatTimestamp(ts) {
|
|
134
|
+
if (!ts) return 'unknown';
|
|
135
|
+
const date = new Date(ts * 1000);
|
|
136
|
+
return date.toLocaleString();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// Readline Helpers
|
|
141
|
+
// ============================================================================
|
|
142
|
+
|
|
143
|
+
function createRl() {
|
|
144
|
+
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function question(rl, q) {
|
|
148
|
+
return new Promise((res) => rl.question(q, res));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function askMnemonic(rl, promptText) {
|
|
152
|
+
console.log(`\n${C.cyan}${promptText}${C.reset}`);
|
|
153
|
+
console.log(`${C.dim}Enter your 12 or 24-word passphrase, one space-separated line:${C.reset}`);
|
|
154
|
+
const raw = await question(rl, ` > ${C.reset}`);
|
|
155
|
+
return raw.trim().toLowerCase();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function askConfirm(rl, text) {
|
|
159
|
+
const ans = await question(rl, `\n${C.yellow}${text} [y/N]${C.reset} > `);
|
|
160
|
+
return ans.trim().toLowerCase().startsWith('y');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// NFT SDK Fetchers (REAL RPC CALLS)
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Fetch NFT details via SDK
|
|
169
|
+
* REAL RPC: GET /v1/nft/<id>
|
|
170
|
+
*/
|
|
171
|
+
async function fetchNFT(rpcUrl, nftId) {
|
|
172
|
+
const client = createClient(rpcUrl);
|
|
173
|
+
try {
|
|
174
|
+
const nft = await client.getNFT(nftId);
|
|
175
|
+
if (!nft || nft.error) return null;
|
|
176
|
+
return {
|
|
177
|
+
id: nft.id || nftId,
|
|
178
|
+
creator: nft.creator || nft.mint_authority,
|
|
179
|
+
metadata: nft.metadata_url || nft.metadata,
|
|
180
|
+
royalties: nft.royalties || nft.royalty_bps || 0,
|
|
181
|
+
supply: nft.supply || nft.current_supply || 0,
|
|
182
|
+
maxSupply: nft.max_supply,
|
|
183
|
+
createdAt: nft.created_at,
|
|
184
|
+
updateAuthority: nft.update_authority,
|
|
185
|
+
};
|
|
186
|
+
} catch (err) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Fetch NFT holdings for an address via SDK
|
|
193
|
+
* REAL RPC: GET /v1/nft-holdings/<addr>
|
|
194
|
+
*/
|
|
195
|
+
async function fetchNFTHoldings(rpcUrl, address) {
|
|
196
|
+
const client = createClient(rpcUrl);
|
|
197
|
+
try {
|
|
198
|
+
const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
|
|
199
|
+
const holdings = await client.getNFTHoldings(rawAddr);
|
|
200
|
+
if (!Array.isArray(holdings)) return [];
|
|
201
|
+
return holdings.map(h => ({
|
|
202
|
+
nftId: h.nft_id || h.id || h.mint,
|
|
203
|
+
amount: h.amount || h.balance || 1,
|
|
204
|
+
acquiredAt: h.acquired_at,
|
|
205
|
+
metadata: h.metadata_url,
|
|
206
|
+
}));
|
|
207
|
+
} catch (err) {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ============================================================================
|
|
213
|
+
// NFT Create Command
|
|
214
|
+
// ============================================================================
|
|
215
|
+
|
|
216
|
+
async function nftCreate(args) {
|
|
217
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
218
|
+
const isJson = args.json || false;
|
|
219
|
+
const rl = createRl();
|
|
220
|
+
|
|
221
|
+
// Resolve wallet address
|
|
222
|
+
let address = args.address;
|
|
223
|
+
if (!address) {
|
|
224
|
+
const cfg = loadConfig();
|
|
225
|
+
address = cfg.defaultWallet;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!address) {
|
|
229
|
+
console.log(`\n ${C.red}✗ No wallet address provided.${C.reset}`);
|
|
230
|
+
console.log(` ${C.dim}Set default: aether wallet default --set <addr>${C.reset}\n`);
|
|
231
|
+
rl.close();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const wallet = loadWallet(address);
|
|
236
|
+
if (!wallet) {
|
|
237
|
+
console.log(`\n ${C.red}✗ Wallet not found locally: ${address}${C.reset}`);
|
|
238
|
+
console.log(` ${C.dim}Import it: aether wallet import${C.reset}\n`);
|
|
239
|
+
rl.close();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Get metadata URL
|
|
244
|
+
let metadataUrl = args.metadata;
|
|
245
|
+
if (!metadataUrl) {
|
|
246
|
+
console.log(`\n${C.bright}${C.cyan}── Create NFT ────────────────────────────────────────────${C.reset}\n`);
|
|
247
|
+
console.log(` ${C.dim}Enter metadata URL (IPFS/Arweave/HTTPS):${C.reset}`);
|
|
248
|
+
metadataUrl = await question(rl, ` Metadata URL > ${C.reset}`);
|
|
249
|
+
if (!metadataUrl) {
|
|
250
|
+
console.log(`\n ${C.red}✗ Metadata URL is required.${C.reset}\n`);
|
|
251
|
+
rl.close();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Get royalties
|
|
257
|
+
let royalties = args.royalties || 0;
|
|
258
|
+
if (!args.royalties && !isJson) {
|
|
259
|
+
const roy = await question(rl, ` ${C.dim}Royalties (basis points, 0-10000) [0]:${C.reset} `);
|
|
260
|
+
royalties = parseInt(roy, 10) || 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (royalties < 0 || royalties > 10000) {
|
|
264
|
+
console.log(`\n ${C.red}✗ Royalties must be between 0 and 10000 basis points.${C.reset}\n`);
|
|
265
|
+
rl.close();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Summary
|
|
270
|
+
console.log(`\n ${C.green}★${C.reset} Creator: ${C.bright}${address}${C.reset}`);
|
|
271
|
+
console.log(` ${C.green}★${C.reset} Metadata: ${C.bright}${metadataUrl}${C.reset}`);
|
|
272
|
+
console.log(` ${C.green}★${C.reset} Royalties: ${C.bright}${royalties} bps (${(royalties / 100).toFixed(2)}%)${C.reset}`);
|
|
273
|
+
console.log();
|
|
274
|
+
|
|
275
|
+
if (args.dryRun) {
|
|
276
|
+
console.log(` ${C.yellow}⚠ Dry run - no transaction submitted${C.reset}\n`);
|
|
277
|
+
rl.close();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Get mnemonic for signing
|
|
282
|
+
let keyPair;
|
|
283
|
+
try {
|
|
284
|
+
const mnemonic = await askMnemonic(rl, 'Enter your wallet passphrase to sign the NFT creation');
|
|
285
|
+
keyPair = deriveKeypair(mnemonic);
|
|
286
|
+
const derivedAddress = formatAddress(keyPair.publicKey);
|
|
287
|
+
if (derivedAddress !== address) {
|
|
288
|
+
console.log(`\n ${C.red}✗ Passphrase mismatch!${C.reset}`);
|
|
289
|
+
console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
|
|
290
|
+
console.log(` ${C.dim} Expected: ${address}${C.reset}\n`);
|
|
291
|
+
rl.close();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
} catch (e) {
|
|
295
|
+
console.log(`\n ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}\n`);
|
|
296
|
+
rl.close();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const confirm = await askConfirm(rl, 'Create this NFT?');
|
|
301
|
+
if (!confirm) {
|
|
302
|
+
console.log(`\n ${C.dim}Cancelled.${C.reset}\n`);
|
|
303
|
+
rl.close();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
rl.close();
|
|
307
|
+
|
|
308
|
+
// Build and submit transaction via SDK
|
|
309
|
+
const client = createClient(rpc);
|
|
310
|
+
const slot = await client.getSlot().catch(() => 0);
|
|
311
|
+
const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
|
|
312
|
+
|
|
313
|
+
const tx = {
|
|
314
|
+
signer: rawAddr,
|
|
315
|
+
tx_type: 'CreateNFT',
|
|
316
|
+
payload: {
|
|
317
|
+
type: 'CreateNFT',
|
|
318
|
+
data: {
|
|
319
|
+
metadata_url: metadataUrl,
|
|
320
|
+
royalties: royalties,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
fee: 10000, // Higher fee for NFT creation
|
|
324
|
+
slot: slot,
|
|
325
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
tx.signature = signTransaction(tx, keyPair.secretKey);
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
if (!isJson) {
|
|
332
|
+
console.log(`\n ${C.dim}Submitting via SDK to ${rpc}...${C.reset}`);
|
|
333
|
+
}
|
|
334
|
+
const result = await client.sendTransaction(tx);
|
|
335
|
+
|
|
336
|
+
if (result.error) {
|
|
337
|
+
throw new Error(result.error.message || JSON.stringify(result.error));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Generate NFT ID from result
|
|
341
|
+
const nftId = result.nft_id || result.mint || `NFT-${result.signature?.slice(0, 16) || 'UNKNOWN'}`;
|
|
342
|
+
|
|
343
|
+
if (isJson) {
|
|
344
|
+
console.log(JSON.stringify({
|
|
345
|
+
success: true,
|
|
346
|
+
nft_id: nftId,
|
|
347
|
+
creator: address,
|
|
348
|
+
metadata_url: metadataUrl,
|
|
349
|
+
royalties_bps: royalties,
|
|
350
|
+
tx_signature: result.signature || result.txid,
|
|
351
|
+
slot: result.slot || slot,
|
|
352
|
+
rpc,
|
|
353
|
+
cli_version: CLI_VERSION,
|
|
354
|
+
timestamp: new Date().toISOString(),
|
|
355
|
+
}, null, 2));
|
|
356
|
+
} else {
|
|
357
|
+
console.log(`\n${C.green}✓ NFT created successfully!${C.reset}\n`);
|
|
358
|
+
console.log(` ${C.green}★${C.reset} NFT ID: ${C.bright}${C.cyan}${nftId}${C.reset}`);
|
|
359
|
+
console.log(` ${C.green}★${C.reset} Creator: ${address}`);
|
|
360
|
+
console.log(` ${C.green}★${C.reset} Metadata: ${metadataUrl}`);
|
|
361
|
+
console.log(` ${C.green}★${C.reset} Royalties: ${(royalties / 100).toFixed(2)}%`);
|
|
362
|
+
if (result.signature || result.txid) {
|
|
363
|
+
console.log(` ${C.dim} Tx Signature: ${(result.signature || result.txid).slice(0, 40)}...${C.reset}`);
|
|
364
|
+
}
|
|
365
|
+
console.log(` ${C.dim} Slot: ${result.slot || slot}${C.reset}`);
|
|
366
|
+
console.log();
|
|
367
|
+
console.log(` ${C.dim}Next steps:${C.reset}`);
|
|
368
|
+
console.log(` ${C.cyan}aether nft mint --nft ${nftId} --amount 1${C.reset}`);
|
|
369
|
+
console.log(` ${C.cyan}aether nft info --nft ${nftId}${C.reset}`);
|
|
370
|
+
console.log();
|
|
371
|
+
}
|
|
372
|
+
} catch (err) {
|
|
373
|
+
if (isJson) {
|
|
374
|
+
console.log(JSON.stringify({
|
|
375
|
+
success: false,
|
|
376
|
+
error: err.message,
|
|
377
|
+
creator: address,
|
|
378
|
+
metadata_url: metadataUrl,
|
|
379
|
+
}, null, 2));
|
|
380
|
+
} else {
|
|
381
|
+
console.log(`\n ${C.red}✗ NFT creation failed: ${err.message}${C.reset}\n`);
|
|
382
|
+
}
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ============================================================================
|
|
388
|
+
// NFT Mint Command
|
|
389
|
+
// ============================================================================
|
|
390
|
+
|
|
391
|
+
async function nftMint(args) {
|
|
392
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
393
|
+
const isJson = args.json || false;
|
|
394
|
+
const rl = createRl();
|
|
395
|
+
|
|
396
|
+
// Resolve wallet address
|
|
397
|
+
let address = args.address;
|
|
398
|
+
if (!address) {
|
|
399
|
+
const cfg = loadConfig();
|
|
400
|
+
address = cfg.defaultWallet;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (!address) {
|
|
404
|
+
console.log(`\n ${C.red}✗ No wallet address provided.${C.reset}\n`);
|
|
405
|
+
rl.close();
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const wallet = loadWallet(address);
|
|
410
|
+
if (!wallet) {
|
|
411
|
+
console.log(`\n ${C.red}✗ Wallet not found locally: ${address}${C.reset}\n`);
|
|
412
|
+
rl.close();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Get NFT ID
|
|
417
|
+
let nftId = args.nft;
|
|
418
|
+
if (!nftId) {
|
|
419
|
+
console.log(`\n${C.bright}${C.cyan}── Mint NFT ─────────────────────────────────────────────${C.reset}\n`);
|
|
420
|
+
console.log(` ${C.dim}Enter NFT ID to mint:${C.reset}`);
|
|
421
|
+
nftId = await question(rl, ` NFT ID > ${C.reset}`);
|
|
422
|
+
if (!nftId) {
|
|
423
|
+
console.log(`\n ${C.red}✗ NFT ID is required.${C.reset}\n`);
|
|
424
|
+
rl.close();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Get amount
|
|
430
|
+
let amount = args.amount;
|
|
431
|
+
if (!amount) {
|
|
432
|
+
const amt = await question(rl, ` ${C.dim}Amount to mint [1]:${C.reset} `);
|
|
433
|
+
amount = parseInt(amt, 10) || 1;
|
|
434
|
+
} else {
|
|
435
|
+
amount = parseInt(amount, 10);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (isNaN(amount) || amount < 1) {
|
|
439
|
+
console.log(`\n ${C.red}✗ Invalid amount.${C.reset}\n`);
|
|
440
|
+
rl.close();
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Get recipient (optional, defaults to self)
|
|
445
|
+
let recipient = args.to || address;
|
|
446
|
+
|
|
447
|
+
// Verify NFT exists
|
|
448
|
+
const nftInfo = await fetchNFT(rpc, nftId);
|
|
449
|
+
if (!nftInfo && !isJson) {
|
|
450
|
+
console.log(`\n ${C.yellow}⚠ Warning: Could not verify NFT exists.${C.reset}`);
|
|
451
|
+
console.log(` ${C.dim} Continuing anyway...${C.reset}\n`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Summary
|
|
455
|
+
console.log(`\n ${C.green}★${C.reset} NFT: ${C.bright}${nftId}${C.reset}`);
|
|
456
|
+
if (nftInfo) {
|
|
457
|
+
console.log(` ${C.green}★${C.reset} Creator: ${shortAddress(nftInfo.creator)}`);
|
|
458
|
+
console.log(` ${C.green}★${C.reset} Current Supply: ${nftInfo.supply}`);
|
|
459
|
+
}
|
|
460
|
+
console.log(` ${C.green}★${C.reset} Amount: ${C.bright}${amount}${C.reset}`);
|
|
461
|
+
console.log(` ${C.green}★${C.reset} Recipient: ${C.bright}${recipient}${C.reset}`);
|
|
462
|
+
console.log();
|
|
463
|
+
|
|
464
|
+
if (args.dryRun) {
|
|
465
|
+
console.log(` ${C.yellow}⚠ Dry run - no transaction submitted${C.reset}\n`);
|
|
466
|
+
rl.close();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Get mnemonic for signing
|
|
471
|
+
let keyPair;
|
|
472
|
+
try {
|
|
473
|
+
const mnemonic = await askMnemonic(rl, 'Enter your wallet passphrase to sign the mint');
|
|
474
|
+
keyPair = deriveKeypair(mnemonic);
|
|
475
|
+
const derivedAddress = formatAddress(keyPair.publicKey);
|
|
476
|
+
if (derivedAddress !== address) {
|
|
477
|
+
console.log(`\n ${C.red}✗ Passphrase mismatch!${C.reset}\n`);
|
|
478
|
+
rl.close();
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
} catch (e) {
|
|
482
|
+
console.log(`\n ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}\n`);
|
|
483
|
+
rl.close();
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const confirm = await askConfirm(rl, 'Mint these NFTs?');
|
|
488
|
+
if (!confirm) {
|
|
489
|
+
console.log(`\n ${C.dim}Cancelled.${C.reset}\n`);
|
|
490
|
+
rl.close();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
rl.close();
|
|
494
|
+
|
|
495
|
+
// Build and submit transaction via SDK
|
|
496
|
+
const client = createClient(rpc);
|
|
497
|
+
const slot = await client.getSlot().catch(() => 0);
|
|
498
|
+
const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
|
|
499
|
+
const rawRecipient = recipient.startsWith('ATH') ? recipient.slice(3) : recipient;
|
|
500
|
+
|
|
501
|
+
const tx = {
|
|
502
|
+
signer: rawAddr,
|
|
503
|
+
tx_type: 'MintNFT',
|
|
504
|
+
payload: {
|
|
505
|
+
type: 'MintNFT',
|
|
506
|
+
data: {
|
|
507
|
+
nft_id: nftId,
|
|
508
|
+
amount: amount,
|
|
509
|
+
recipient: rawRecipient,
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
fee: 5000,
|
|
513
|
+
slot: slot,
|
|
514
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
tx.signature = signTransaction(tx, keyPair.secretKey);
|
|
518
|
+
|
|
519
|
+
try {
|
|
520
|
+
if (!isJson) {
|
|
521
|
+
console.log(`\n ${C.dim}Submitting via SDK...${C.reset}`);
|
|
522
|
+
}
|
|
523
|
+
const result = await client.sendTransaction(tx);
|
|
524
|
+
|
|
525
|
+
if (result.error) {
|
|
526
|
+
throw new Error(result.error.message || JSON.stringify(result.error));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (isJson) {
|
|
530
|
+
console.log(JSON.stringify({
|
|
531
|
+
success: true,
|
|
532
|
+
nft_id: nftId,
|
|
533
|
+
amount: amount,
|
|
534
|
+
recipient: recipient,
|
|
535
|
+
minter: address,
|
|
536
|
+
tx_signature: result.signature || result.txid,
|
|
537
|
+
slot: result.slot || slot,
|
|
538
|
+
rpc,
|
|
539
|
+
cli_version: CLI_VERSION,
|
|
540
|
+
timestamp: new Date().toISOString(),
|
|
541
|
+
}, null, 2));
|
|
542
|
+
} else {
|
|
543
|
+
console.log(`\n${C.green}✓ NFT minted successfully!${C.reset}\n`);
|
|
544
|
+
console.log(` ${C.green}★${C.reset} NFT ID: ${C.cyan}${nftId}${C.reset}`);
|
|
545
|
+
console.log(` ${C.green}★${C.reset} Amount: ${amount}`);
|
|
546
|
+
console.log(` ${C.green}★${C.reset} Recipient: ${recipient}`);
|
|
547
|
+
if (result.signature || result.txid) {
|
|
548
|
+
console.log(` ${C.dim} Tx: ${(result.signature || result.txid).slice(0, 40)}...${C.reset}`);
|
|
549
|
+
}
|
|
550
|
+
console.log(` ${C.dim} Slot: ${result.slot || slot}${C.reset}`);
|
|
551
|
+
console.log();
|
|
552
|
+
}
|
|
553
|
+
} catch (err) {
|
|
554
|
+
if (isJson) {
|
|
555
|
+
console.log(JSON.stringify({
|
|
556
|
+
success: false,
|
|
557
|
+
error: err.message,
|
|
558
|
+
nft_id: nftId,
|
|
559
|
+
}, null, 2));
|
|
560
|
+
} else {
|
|
561
|
+
console.log(`\n ${C.red}✗ NFT mint failed: ${err.message}${C.reset}\n`);
|
|
562
|
+
}
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ============================================================================
|
|
568
|
+
// NFT Transfer Command
|
|
569
|
+
// ============================================================================
|
|
570
|
+
|
|
571
|
+
async function nftTransfer(args) {
|
|
572
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
573
|
+
const isJson = args.json || false;
|
|
574
|
+
const rl = createRl();
|
|
575
|
+
|
|
576
|
+
// Resolve wallet address
|
|
577
|
+
let address = args.address;
|
|
578
|
+
if (!address) {
|
|
579
|
+
const cfg = loadConfig();
|
|
580
|
+
address = cfg.defaultWallet;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (!address) {
|
|
584
|
+
console.log(`\n ${C.red}✗ No wallet address provided.${C.reset}\n`);
|
|
585
|
+
rl.close();
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const wallet = loadWallet(address);
|
|
590
|
+
if (!wallet) {
|
|
591
|
+
console.log(`\n ${C.red}✗ Wallet not found: ${address}${C.reset}\n`);
|
|
592
|
+
rl.close();
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Get NFT ID
|
|
597
|
+
let nftId = args.nft;
|
|
598
|
+
if (!nftId) {
|
|
599
|
+
console.log(`\n${C.bright}${C.cyan}── Transfer NFT ─────────────────────────────────────────${C.reset}\n`);
|
|
600
|
+
console.log(` ${C.dim}Enter NFT ID to transfer:${C.reset}`);
|
|
601
|
+
nftId = await question(rl, ` NFT ID > ${C.reset}`);
|
|
602
|
+
if (!nftId) {
|
|
603
|
+
console.log(`\n ${C.red}✗ NFT ID is required.${C.reset}\n`);
|
|
604
|
+
rl.close();
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Get recipient
|
|
610
|
+
let recipient = args.to;
|
|
611
|
+
if (!recipient) {
|
|
612
|
+
console.log(` ${C.dim}Enter recipient address:${C.reset}`);
|
|
613
|
+
recipient = await question(rl, ` Recipient > ${C.reset}`);
|
|
614
|
+
if (!recipient) {
|
|
615
|
+
console.log(`\n ${C.red}✗ Recipient address is required.${C.reset}\n`);
|
|
616
|
+
rl.close();
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Get amount (for semi-fungible NFTs)
|
|
622
|
+
let amount = args.amount || 1;
|
|
623
|
+
|
|
624
|
+
// Summary
|
|
625
|
+
console.log(`\n ${C.green}★${C.reset} NFT: ${C.bright}${nftId}${C.reset}`);
|
|
626
|
+
console.log(` ${C.green}★${C.reset} From: ${address}`);
|
|
627
|
+
console.log(` ${C.green}★${C.reset} To: ${C.bright}${C.cyan}${recipient}${C.reset}`);
|
|
628
|
+
console.log(` ${C.green}★${C.reset} Amount: ${amount}`);
|
|
629
|
+
console.log();
|
|
630
|
+
|
|
631
|
+
if (args.dryRun) {
|
|
632
|
+
console.log(` ${C.yellow}⚠ Dry run - no transaction submitted${C.reset}\n`);
|
|
633
|
+
rl.close();
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Get mnemonic for signing
|
|
638
|
+
let keyPair;
|
|
639
|
+
try {
|
|
640
|
+
const mnemonic = await askMnemonic(rl, 'Enter your wallet passphrase to sign the transfer');
|
|
641
|
+
keyPair = deriveKeypair(mnemonic);
|
|
642
|
+
const derivedAddress = formatAddress(keyPair.publicKey);
|
|
643
|
+
if (derivedAddress !== address) {
|
|
644
|
+
console.log(`\n ${C.red}✗ Passphrase mismatch!${C.reset}\n`);
|
|
645
|
+
rl.close();
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
} catch (e) {
|
|
649
|
+
console.log(`\n ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}\n`);
|
|
650
|
+
rl.close();
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const confirm = await askConfirm(rl, `Transfer ${amount} of ${nftId} to ${shortAddress(recipient)}?`);
|
|
655
|
+
if (!confirm) {
|
|
656
|
+
console.log(`\n ${C.dim}Cancelled.${C.reset}\n`);
|
|
657
|
+
rl.close();
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
rl.close();
|
|
661
|
+
|
|
662
|
+
// Build and submit transaction via SDK
|
|
663
|
+
const client = createClient(rpc);
|
|
664
|
+
const slot = await client.getSlot().catch(() => 0);
|
|
665
|
+
const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
|
|
666
|
+
const rawRecipient = recipient.startsWith('ATH') ? recipient.slice(3) : recipient;
|
|
667
|
+
|
|
668
|
+
const tx = {
|
|
669
|
+
signer: rawAddr,
|
|
670
|
+
tx_type: 'TransferNFT',
|
|
671
|
+
payload: {
|
|
672
|
+
type: 'TransferNFT',
|
|
673
|
+
data: {
|
|
674
|
+
nft_id: nftId,
|
|
675
|
+
recipient: rawRecipient,
|
|
676
|
+
amount: amount,
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
fee: 5000,
|
|
680
|
+
slot: slot,
|
|
681
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
tx.signature = signTransaction(tx, keyPair.secretKey);
|
|
685
|
+
|
|
686
|
+
try {
|
|
687
|
+
if (!isJson) {
|
|
688
|
+
console.log(`\n ${C.dim}Submitting via SDK...${C.reset}`);
|
|
689
|
+
}
|
|
690
|
+
const result = await client.sendTransaction(tx);
|
|
691
|
+
|
|
692
|
+
if (result.error) {
|
|
693
|
+
throw new Error(result.error.message || JSON.stringify(result.error));
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (isJson) {
|
|
697
|
+
console.log(JSON.stringify({
|
|
698
|
+
success: true,
|
|
699
|
+
nft_id: nftId,
|
|
700
|
+
amount: amount,
|
|
701
|
+
from: address,
|
|
702
|
+
to: recipient,
|
|
703
|
+
tx_signature: result.signature || result.txid,
|
|
704
|
+
slot: result.slot || slot,
|
|
705
|
+
rpc,
|
|
706
|
+
cli_version: CLI_VERSION,
|
|
707
|
+
timestamp: new Date().toISOString(),
|
|
708
|
+
}, null, 2));
|
|
709
|
+
} else {
|
|
710
|
+
console.log(`\n${C.green}✓ NFT transferred successfully!${C.reset}\n`);
|
|
711
|
+
console.log(` ${C.green}★${C.reset} NFT ID: ${C.cyan}${nftId}${C.reset}`);
|
|
712
|
+
console.log(` ${C.green}★${C.reset} From: ${shortAddress(address)}`);
|
|
713
|
+
console.log(` ${C.green}★${C.reset} To: ${C.cyan}${shortAddress(recipient)}${C.reset}`);
|
|
714
|
+
console.log(` ${C.green}★${C.reset} Amount: ${amount}`);
|
|
715
|
+
if (result.signature || result.txid) {
|
|
716
|
+
console.log(` ${C.dim} Tx: ${(result.signature || result.txid).slice(0, 40)}...${C.reset}`);
|
|
717
|
+
}
|
|
718
|
+
console.log();
|
|
719
|
+
}
|
|
720
|
+
} catch (err) {
|
|
721
|
+
if (isJson) {
|
|
722
|
+
console.log(JSON.stringify({
|
|
723
|
+
success: false,
|
|
724
|
+
error: err.message,
|
|
725
|
+
nft_id: nftId,
|
|
726
|
+
}, null, 2));
|
|
727
|
+
} else {
|
|
728
|
+
console.log(`\n ${C.red}✗ NFT transfer failed: ${err.message}${C.reset}\n`);
|
|
729
|
+
}
|
|
730
|
+
process.exit(1);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// ============================================================================
|
|
735
|
+
// NFT Update Metadata Command
|
|
736
|
+
// ============================================================================
|
|
737
|
+
|
|
738
|
+
async function nftUpdate(args) {
|
|
739
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
740
|
+
const isJson = args.json || false;
|
|
741
|
+
const rl = createRl();
|
|
742
|
+
|
|
743
|
+
// Resolve wallet address
|
|
744
|
+
let address = args.address;
|
|
745
|
+
if (!address) {
|
|
746
|
+
const cfg = loadConfig();
|
|
747
|
+
address = cfg.defaultWallet;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (!address) {
|
|
751
|
+
console.log(`\n ${C.red}✗ No wallet address provided.${C.reset}\n`);
|
|
752
|
+
rl.close();
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const wallet = loadWallet(address);
|
|
757
|
+
if (!wallet) {
|
|
758
|
+
console.log(`\n ${C.red}✗ Wallet not found: ${address}${C.reset}\n`);
|
|
759
|
+
rl.close();
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Get NFT ID
|
|
764
|
+
let nftId = args.nft;
|
|
765
|
+
if (!nftId) {
|
|
766
|
+
console.log(`\n${C.bright}${C.cyan}── Update NFT Metadata ──────────────────────────────────${C.reset}\n`);
|
|
767
|
+
console.log(` ${C.dim}Enter NFT ID to update:${C.reset}`);
|
|
768
|
+
nftId = await question(rl, ` NFT ID > ${C.reset}`);
|
|
769
|
+
if (!nftId) {
|
|
770
|
+
console.log(`\n ${C.red}✗ NFT ID is required.${C.reset}\n`);
|
|
771
|
+
rl.close();
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Get new metadata URL
|
|
777
|
+
let metadataUrl = args.metadata;
|
|
778
|
+
if (!metadataUrl) {
|
|
779
|
+
console.log(` ${C.dim}Enter new metadata URL:${C.reset}`);
|
|
780
|
+
metadataUrl = await question(rl, ` New Metadata URL > ${C.reset}`);
|
|
781
|
+
if (!metadataUrl) {
|
|
782
|
+
console.log(`\n ${C.red}✗ Metadata URL is required.${C.reset}\n`);
|
|
783
|
+
rl.close();
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Verify NFT exists
|
|
789
|
+
const nftInfo = await fetchNFT(rpc, nftId);
|
|
790
|
+
if (!nftInfo) {
|
|
791
|
+
console.log(`\n ${C.red}✗ NFT not found: ${nftId}${C.reset}\n`);
|
|
792
|
+
rl.close();
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Check if user is update authority
|
|
797
|
+
if (nftInfo.updateAuthority && nftInfo.updateAuthority !== address) {
|
|
798
|
+
console.log(`\n ${C.yellow}⚠ Warning: You may not be the update authority.${C.reset}`);
|
|
799
|
+
console.log(` ${C.dim} Update Authority: ${nftInfo.updateAuthority}${C.reset}`);
|
|
800
|
+
console.log(` ${C.dim} Your Address: ${address}${C.reset}\n`);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Summary
|
|
804
|
+
console.log(`\n ${C.green}★${C.reset} NFT: ${C.bright}${nftId}${C.reset}`);
|
|
805
|
+
console.log(` ${C.green}★${C.reset} Current: ${C.dim}${nftInfo.metadata}${C.reset}`);
|
|
806
|
+
console.log(` ${C.green}★${C.reset} New Metadata: ${C.bright}${metadataUrl}${C.reset}`);
|
|
807
|
+
console.log();
|
|
808
|
+
|
|
809
|
+
if (args.dryRun) {
|
|
810
|
+
console.log(` ${C.yellow}⚠ Dry run - no transaction submitted${C.reset}\n`);
|
|
811
|
+
rl.close();
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Get mnemonic for signing
|
|
816
|
+
let keyPair;
|
|
817
|
+
try {
|
|
818
|
+
const mnemonic = await askMnemonic(rl, 'Enter your wallet passphrase to sign the update');
|
|
819
|
+
keyPair = deriveKeypair(mnemonic);
|
|
820
|
+
const derivedAddress = formatAddress(keyPair.publicKey);
|
|
821
|
+
if (derivedAddress !== address) {
|
|
822
|
+
console.log(`\n ${C.red}✗ Passphrase mismatch!${C.reset}\n`);
|
|
823
|
+
rl.close();
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
} catch (e) {
|
|
827
|
+
console.log(`\n ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}\n`);
|
|
828
|
+
rl.close();
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const confirm = await askConfirm(rl, 'Update metadata?');
|
|
833
|
+
if (!confirm) {
|
|
834
|
+
console.log(`\n ${C.dim}Cancelled.${C.reset}\n`);
|
|
835
|
+
rl.close();
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
rl.close();
|
|
839
|
+
|
|
840
|
+
// Build and submit transaction via SDK
|
|
841
|
+
const client = createClient(rpc);
|
|
842
|
+
const slot = await client.getSlot().catch(() => 0);
|
|
843
|
+
const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
|
|
844
|
+
|
|
845
|
+
const tx = {
|
|
846
|
+
signer: rawAddr,
|
|
847
|
+
tx_type: 'UpdateMetadata',
|
|
848
|
+
payload: {
|
|
849
|
+
type: 'UpdateMetadata',
|
|
850
|
+
data: {
|
|
851
|
+
nft_id: nftId,
|
|
852
|
+
metadata_url: metadataUrl,
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
fee: 5000,
|
|
856
|
+
slot: slot,
|
|
857
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
tx.signature = signTransaction(tx, keyPair.secretKey);
|
|
861
|
+
|
|
862
|
+
try {
|
|
863
|
+
if (!isJson) {
|
|
864
|
+
console.log(`\n ${C.dim}Submitting via SDK...${C.reset}`);
|
|
865
|
+
}
|
|
866
|
+
const result = await client.sendTransaction(tx);
|
|
867
|
+
|
|
868
|
+
if (result.error) {
|
|
869
|
+
throw new Error(result.error.message || JSON.stringify(result.error));
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (isJson) {
|
|
873
|
+
console.log(JSON.stringify({
|
|
874
|
+
success: true,
|
|
875
|
+
nft_id: nftId,
|
|
876
|
+
new_metadata: metadataUrl,
|
|
877
|
+
updater: address,
|
|
878
|
+
tx_signature: result.signature || result.txid,
|
|
879
|
+
slot: result.slot || slot,
|
|
880
|
+
rpc,
|
|
881
|
+
cli_version: CLI_VERSION,
|
|
882
|
+
timestamp: new Date().toISOString(),
|
|
883
|
+
}, null, 2));
|
|
884
|
+
} else {
|
|
885
|
+
console.log(`\n${C.green}✓ NFT metadata updated!${C.reset}\n`);
|
|
886
|
+
console.log(` ${C.green}★${C.reset} NFT ID: ${C.cyan}${nftId}${C.reset}`);
|
|
887
|
+
console.log(` ${C.green}★${C.reset} New Metadata: ${metadataUrl}`);
|
|
888
|
+
if (result.signature || result.txid) {
|
|
889
|
+
console.log(` ${C.dim} Tx: ${(result.signature || result.txid).slice(0, 40)}...${C.reset}`);
|
|
890
|
+
}
|
|
891
|
+
console.log();
|
|
892
|
+
}
|
|
893
|
+
} catch (err) {
|
|
894
|
+
if (isJson) {
|
|
895
|
+
console.log(JSON.stringify({
|
|
896
|
+
success: false,
|
|
897
|
+
error: err.message,
|
|
898
|
+
nft_id: nftId,
|
|
899
|
+
}, null, 2));
|
|
900
|
+
} else {
|
|
901
|
+
console.log(`\n ${C.red}✗ Metadata update failed: ${err.message}${C.reset}\n`);
|
|
902
|
+
}
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// ============================================================================
|
|
908
|
+
// NFT List Command
|
|
909
|
+
// ============================================================================
|
|
910
|
+
|
|
911
|
+
async function nftList(args) {
|
|
912
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
913
|
+
const isJson = args.json || false;
|
|
914
|
+
let address = args.address;
|
|
915
|
+
|
|
916
|
+
// Resolve address
|
|
917
|
+
if (!address) {
|
|
918
|
+
const cfg = loadConfig();
|
|
919
|
+
address = cfg.defaultWallet;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (!address) {
|
|
923
|
+
if (isJson) {
|
|
924
|
+
console.log(JSON.stringify({ error: 'No address provided and no default wallet' }, null, 2));
|
|
925
|
+
} else {
|
|
926
|
+
console.log(`\n ${C.red}✗ No wallet address specified.${C.reset}\n`);
|
|
927
|
+
}
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Fetch NFT holdings via SDK (REAL RPC)
|
|
932
|
+
const holdings = await fetchNFTHoldings(rpc, address);
|
|
933
|
+
|
|
934
|
+
if (isJson) {
|
|
935
|
+
console.log(JSON.stringify({
|
|
936
|
+
address,
|
|
937
|
+
rpc,
|
|
938
|
+
holdings: holdings.map(h => ({
|
|
939
|
+
nft_id: h.nftId,
|
|
940
|
+
amount: h.amount,
|
|
941
|
+
metadata: h.metadata,
|
|
942
|
+
acquired_at: h.acquiredAt,
|
|
943
|
+
})),
|
|
944
|
+
total_nfts: holdings.length,
|
|
945
|
+
cli_version: CLI_VERSION,
|
|
946
|
+
timestamp: new Date().toISOString(),
|
|
947
|
+
}, null, 2));
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
console.log(`\n${C.bright}${C.cyan}╔═══════════════════════════════════════════════════════════════════╗${C.reset}`);
|
|
952
|
+
console.log(`${C.bright}${C.cyan}║ NFT HOLDINGS — ${shortAddress(address).padEnd(30)}║${C.reset}`);
|
|
953
|
+
console.log(`${C.bright}${C.cyan}╚═══════════════════════════════════════════════════════════════════╝${C.reset}\n`);
|
|
954
|
+
console.log(` ${C.dim}RPC: ${rpc}${C.reset}\n`);
|
|
955
|
+
|
|
956
|
+
if (holdings.length === 0) {
|
|
957
|
+
console.log(` ${C.yellow}⚠ No NFTs found for this wallet.${C.reset}`);
|
|
958
|
+
console.log(` ${C.dim}Create an NFT:${C.reset}`);
|
|
959
|
+
console.log(` ${C.cyan}aether nft create --metadata <url>${C.reset}\n`);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
console.log(` ${C.dim}┌─────────────────────────────────────────────────────────────────────┐${C.reset}`);
|
|
964
|
+
console.log(` ${C.dim}│${C.reset} ${C.bright}# NFT ID Amount Metadata${C.reset} ${C.dim}│${C.reset}`);
|
|
965
|
+
console.log(` ${C.dim}├─────────────────────────────────────────────────────────────────────┤${C.reset}`);
|
|
966
|
+
|
|
967
|
+
holdings.forEach((h, i) => {
|
|
968
|
+
const num = (i + 1).toString().padStart(2);
|
|
969
|
+
const shortId = shortAddress(h.nftId).padEnd(30);
|
|
970
|
+
const amt = h.amount.toString().padStart(6);
|
|
971
|
+
const meta = h.metadata ? h.metadata.substring(0, 20) + '...' : 'N/A';
|
|
972
|
+
console.log(` ${C.dim}│${C.reset} ${num} ${shortId} ${amt} ${C.dim}${meta}${C.reset} ${C.dim}│${C.reset}`);
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
console.log(` ${C.dim}└─────────────────────────────────────────────────────────────────────┘${C.reset}`);
|
|
976
|
+
console.log(`\n ${C.bright}Total NFTs:${C.reset} ${holdings.length}`);
|
|
977
|
+
console.log(` ${C.dim}SDK: getNFTHoldings()${C.reset}\n`);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// ============================================================================
|
|
981
|
+
// NFT Info Command
|
|
982
|
+
// ============================================================================
|
|
983
|
+
|
|
984
|
+
async function nftInfo(args) {
|
|
985
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
986
|
+
const isJson = args.json || false;
|
|
987
|
+
const nftId = args.nft;
|
|
988
|
+
|
|
989
|
+
if (!nftId) {
|
|
990
|
+
if (isJson) {
|
|
991
|
+
console.log(JSON.stringify({ error: 'NFT ID required (--nft <id>)' }, null, 2));
|
|
992
|
+
} else {
|
|
993
|
+
console.log(`\n ${C.red}✗ NFT ID required.${C.reset}`);
|
|
994
|
+
console.log(` ${C.dim}Usage: aether nft info --nft <id>${C.reset}\n`);
|
|
995
|
+
}
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// Fetch NFT info via SDK (REAL RPC)
|
|
1000
|
+
const nft = await fetchNFT(rpc, nftId);
|
|
1001
|
+
|
|
1002
|
+
if (!nft) {
|
|
1003
|
+
if (isJson) {
|
|
1004
|
+
console.log(JSON.stringify({ error: 'NFT not found', nft_id: nftId }, null, 2));
|
|
1005
|
+
} else {
|
|
1006
|
+
console.log(`\n ${C.red}✗ NFT not found: ${nftId}${C.reset}\n`);
|
|
1007
|
+
}
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (isJson) {
|
|
1012
|
+
console.log(JSON.stringify({
|
|
1013
|
+
nft_id: nft.id,
|
|
1014
|
+
creator: nft.creator,
|
|
1015
|
+
metadata_url: nft.metadata,
|
|
1016
|
+
royalties_bps: nft.royalties,
|
|
1017
|
+
supply: nft.supply,
|
|
1018
|
+
max_supply: nft.maxSupply,
|
|
1019
|
+
update_authority: nft.updateAuthority,
|
|
1020
|
+
created_at: nft.createdAt,
|
|
1021
|
+
rpc,
|
|
1022
|
+
cli_version: CLI_VERSION,
|
|
1023
|
+
timestamp: new Date().toISOString(),
|
|
1024
|
+
}, null, 2));
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
console.log(`\n${C.bright}${C.cyan}── NFT Details ────────────────────────────────────────────${C.reset}\n`);
|
|
1029
|
+
console.log(` ${C.green}★${C.reset} NFT ID: ${C.bright}${C.cyan}${nft.id}${C.reset}`);
|
|
1030
|
+
console.log(` ${C.green}★${C.reset} Creator: ${shortAddress(nft.creator)}`);
|
|
1031
|
+
console.log(` ${C.green}★${C.reset} Metadata: ${C.blue}${nft.metadata}${C.reset}`);
|
|
1032
|
+
console.log(` ${C.green}★${C.reset} Royalties: ${(nft.royalties / 100).toFixed(2)}%`);
|
|
1033
|
+
console.log(` ${C.green}★${C.reset} Supply: ${nft.supply}${nft.maxSupply ? ' / ' + nft.maxSupply : ''}`);
|
|
1034
|
+
if (nft.updateAuthority) {
|
|
1035
|
+
console.log(` ${C.green}★${C.reset} Update Authority: ${shortAddress(nft.updateAuthority)}`);
|
|
1036
|
+
}
|
|
1037
|
+
if (nft.createdAt) {
|
|
1038
|
+
console.log(` ${C.dim} Created: ${formatTimestamp(nft.createdAt)}${C.reset}`);
|
|
1039
|
+
}
|
|
1040
|
+
console.log();
|
|
1041
|
+
console.log(` ${C.dim}SDK: getNFT()${C.reset}\n`);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// ============================================================================
|
|
1045
|
+
// CLI Args Parser
|
|
1046
|
+
// ============================================================================
|
|
1047
|
+
|
|
1048
|
+
function parseArgs() {
|
|
1049
|
+
const rawArgs = process.argv.slice(3);
|
|
1050
|
+
const subcmd = rawArgs[0] || 'list';
|
|
1051
|
+
const allArgs = rawArgs.slice(1);
|
|
1052
|
+
|
|
1053
|
+
const rpcIndex = allArgs.findIndex(a => a === '--rpc' || a === '-r');
|
|
1054
|
+
const rpc = rpcIndex !== -1 && allArgs[rpcIndex + 1] ? allArgs[rpcIndex + 1] : getDefaultRpc();
|
|
1055
|
+
|
|
1056
|
+
const parsed = {
|
|
1057
|
+
subcmd,
|
|
1058
|
+
rpc,
|
|
1059
|
+
json: allArgs.includes('--json') || allArgs.includes('-j'),
|
|
1060
|
+
dryRun: allArgs.includes('--dry-run'),
|
|
1061
|
+
address: null,
|
|
1062
|
+
nft: null,
|
|
1063
|
+
metadata: null,
|
|
1064
|
+
to: null,
|
|
1065
|
+
amount: null,
|
|
1066
|
+
royalties: null,
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
const addrIdx = allArgs.findIndex(a => a === '--address' || a === '-a');
|
|
1070
|
+
if (addrIdx !== -1 && allArgs[addrIdx + 1]) parsed.address = allArgs[addrIdx + 1];
|
|
1071
|
+
|
|
1072
|
+
const nftIdx = allArgs.findIndex(a => a === '--nft' || a === '-n');
|
|
1073
|
+
if (nftIdx !== -1 && allArgs[nftIdx + 1]) parsed.nft = allArgs[nftIdx + 1];
|
|
1074
|
+
|
|
1075
|
+
const metaIdx = allArgs.findIndex(a => a === '--metadata' || a === '-m');
|
|
1076
|
+
if (metaIdx !== -1 && allArgs[metaIdx + 1]) parsed.metadata = allArgs[metaIdx + 1];
|
|
1077
|
+
|
|
1078
|
+
const toIdx = allArgs.findIndex(a => a === '--to' || a === '-t');
|
|
1079
|
+
if (toIdx !== -1 && allArgs[toIdx + 1]) parsed.to = allArgs[toIdx + 1];
|
|
1080
|
+
|
|
1081
|
+
const amtIdx = allArgs.findIndex(a => a === '--amount' || a === '-x');
|
|
1082
|
+
if (amtIdx !== -1 && allArgs[amtIdx + 1]) parsed.amount = allArgs[amtIdx + 1];
|
|
1083
|
+
|
|
1084
|
+
const royIdx = allArgs.findIndex(a => a === '--royalties' || a === '-r');
|
|
1085
|
+
if (royIdx !== -1 && allArgs[royIdx + 1]) parsed.royalties = parseInt(allArgs[royIdx + 1], 10);
|
|
1086
|
+
|
|
1087
|
+
return parsed;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function showHelp() {
|
|
1091
|
+
console.log(`
|
|
1092
|
+
${C.bright}${C.cyan}aether-cli nft${C.reset} — NFT Management
|
|
1093
|
+
|
|
1094
|
+
${C.bright}USAGE${C.reset}
|
|
1095
|
+
aether nft create --metadata <url> [--royalties <bps>] [--json]
|
|
1096
|
+
aether nft mint --nft <id> --amount <n> [--to <addr>] [--json]
|
|
1097
|
+
aether nft transfer --nft <id> --to <addr> [--amount <n>] [--json]
|
|
1098
|
+
aether nft update --nft <id> --metadata <url> [--json]
|
|
1099
|
+
aether nft list [--address <addr>] [--json]
|
|
1100
|
+
aether nft info --nft <id> [--json]
|
|
1101
|
+
|
|
1102
|
+
${C.bright}COMMANDS${C.reset}
|
|
1103
|
+
create Create a new NFT
|
|
1104
|
+
mint Mint additional supply of an existing NFT
|
|
1105
|
+
transfer Transfer NFT to another address
|
|
1106
|
+
update Update NFT metadata URL
|
|
1107
|
+
list Show all NFTs held by a wallet
|
|
1108
|
+
info Show detailed info about a specific NFT
|
|
1109
|
+
|
|
1110
|
+
${C.bright}OPTIONS${C.reset}
|
|
1111
|
+
--metadata <url> Metadata URL (IPFS/Arweave/HTTPS)
|
|
1112
|
+
--royalties <bps> Royalties in basis points (0-10000)
|
|
1113
|
+
--nft <id> NFT identifier
|
|
1114
|
+
--amount <n> Amount to mint/transfer
|
|
1115
|
+
--to <addr> Recipient address
|
|
1116
|
+
--address <addr> Wallet address (default: configured default)
|
|
1117
|
+
--rpc <url> RPC endpoint
|
|
1118
|
+
--json Output JSON
|
|
1119
|
+
--dry-run Preview without submitting
|
|
1120
|
+
|
|
1121
|
+
${C.bright}EXAMPLES${C.reset}
|
|
1122
|
+
aether nft create --metadata ipfs://Qm... --royalties 500
|
|
1123
|
+
aether nft mint --nft NFTabc... --amount 10 --to ATH...
|
|
1124
|
+
aether nft transfer --nft NFTabc... --to ATH... --amount 1
|
|
1125
|
+
aether nft update --nft NFTabc... --metadata ipfs://QmNew...
|
|
1126
|
+
aether nft list --address ATH...
|
|
1127
|
+
aether nft info --nft NFTabc...
|
|
1128
|
+
|
|
1129
|
+
${C.green}✓ Fully wired to @jellylegsai/aether-sdk${C.reset}
|
|
1130
|
+
`);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// ============================================================================
|
|
1134
|
+
// Main Entry Point
|
|
1135
|
+
// ============================================================================
|
|
1136
|
+
|
|
1137
|
+
async function nftCommand() {
|
|
1138
|
+
const args = parseArgs();
|
|
1139
|
+
|
|
1140
|
+
if (args.subcmd === '--help' || args.subcmd === '-h' || args.subcmd === 'help') {
|
|
1141
|
+
showHelp();
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
switch (args.subcmd) {
|
|
1146
|
+
case 'create':
|
|
1147
|
+
await nftCreate(args);
|
|
1148
|
+
break;
|
|
1149
|
+
case 'mint':
|
|
1150
|
+
await nftMint(args);
|
|
1151
|
+
break;
|
|
1152
|
+
case 'transfer':
|
|
1153
|
+
await nftTransfer(args);
|
|
1154
|
+
break;
|
|
1155
|
+
case 'update':
|
|
1156
|
+
await nftUpdate(args);
|
|
1157
|
+
break;
|
|
1158
|
+
case 'list':
|
|
1159
|
+
await nftList(args);
|
|
1160
|
+
break;
|
|
1161
|
+
case 'info':
|
|
1162
|
+
await nftInfo(args);
|
|
1163
|
+
break;
|
|
1164
|
+
default:
|
|
1165
|
+
console.log(`\n ${C.red}✗ Unknown subcommand: ${args.subcmd}${C.reset}`);
|
|
1166
|
+
showHelp();
|
|
1167
|
+
process.exit(1);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// Export for module use
|
|
1172
|
+
module.exports = { nftCommand };
|
|
1173
|
+
|
|
1174
|
+
// Run if called directly
|
|
1175
|
+
if (require.main === module) {
|
|
1176
|
+
nftCommand().catch(err => {
|
|
1177
|
+
console.error(`\n${C.red}✗ NFT command failed:${C.reset}`, err.message, '\n');
|
|
1178
|
+
process.exit(1);
|
|
1179
|
+
});
|
|
1180
|
+
}
|