hippius 0.1.9__py3-none-any.whl → 0.1.11__py3-none-any.whl

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.
hippius_sdk/cli.py CHANGED
@@ -16,6 +16,8 @@ from typing import Optional, List
16
16
  import getpass
17
17
  import concurrent.futures
18
18
  import threading
19
+ import random
20
+ import uuid
19
21
 
20
22
  # Import SDK components
21
23
  from hippius_sdk import HippiusClient
@@ -431,9 +433,12 @@ def handle_credits(client, account_address):
431
433
  return 0
432
434
 
433
435
 
434
- def handle_files(client, account_address, debug=False, show_all_miners=False):
435
- """Handle the files command"""
436
- print("Retrieving file information...")
436
+ def handle_files(client, account_address, show_all_miners=False):
437
+ """
438
+ Display files stored by a user in a nice format.
439
+
440
+ This command only reads data and doesn't require seed phrase decryption.
441
+ """
437
442
  try:
438
443
  # Get the account address we're querying
439
444
  if account_address is None:
@@ -457,105 +462,89 @@ def handle_files(client, account_address, debug=False, show_all_miners=False):
457
462
  )
458
463
  return 1
459
464
 
460
- if debug:
461
- print("DEBUG MODE: Will show details about CID decoding")
465
+ # Get files for the account using the new profile-based method
466
+ print(f"Retrieving files for account: {account_address}")
467
+ files = client.substrate_client.get_user_files_from_profile(account_address)
462
468
 
463
- # Use the enhanced get_user_files method with our preferences
464
- max_miners = 0 if show_all_miners else 3 # 0 means show all miners
465
- files = client.substrate_client.get_user_files(
466
- account_address,
467
- truncate_miners=True, # Always truncate long miner IDs
468
- max_miners=max_miners, # Use 0 for all or 3 for limited
469
- )
469
+ # Check if any files were found
470
+ if not files:
471
+ print(f"No files found for account: {account_address}")
472
+ return 0
470
473
 
471
- if files:
472
- print(f"\nFound {len(files)} files for account: {account_address}")
473
- print("\n" + "-" * 80)
474
+ print(f"\nFound {len(files)} files for account: {account_address}")
475
+ print("-" * 80)
474
476
 
475
- for i, file in enumerate(files, 1):
477
+ for i, file in enumerate(files, 1):
478
+ try:
476
479
  print(f"File {i}:")
477
480
 
478
- # Format the CID using the SDK method
481
+ # Display file hash/CID
479
482
  file_hash = file.get("file_hash", "Unknown")
480
- formatted_cid = client.format_cid(file_hash)
481
- print(f" File Hash (CID): {formatted_cid}")
483
+ if file_hash is not None:
484
+ formatted_cid = client.format_cid(file_hash)
485
+ print(f" CID: {formatted_cid}")
486
+ else:
487
+ print(f" CID: Unknown (None)")
482
488
 
483
489
  # Display file name
484
- print(f" File Name: {file.get('file_name', 'Unnamed')}")
490
+ file_name = file.get("file_name", "Unnamed")
491
+ print(
492
+ f" File name: {file_name if file_name is not None else 'Unnamed'}"
493
+ )
485
494
 
486
- # Display file size with SDK formatting method if needed
487
- file_size = file.get("file_size", 0)
488
- size_formatted = file.get("size_formatted")
489
- if not size_formatted and file_size > 0:
490
- size_formatted = client.format_size(file_size)
491
- print(f" File Size: {file_size:,} bytes ({size_formatted})")
495
+ # Display file size
496
+ if "size_formatted" in file and file["size_formatted"] is not None:
497
+ size_formatted = file["size_formatted"]
498
+ file_size = file.get("file_size", 0)
499
+ if file_size is not None:
500
+ print(f" File size: {file_size:,} bytes ({size_formatted})")
501
+ else:
502
+ print(f" File size: Unknown")
503
+ else:
504
+ print(f" File size: Unknown")
492
505
 
493
- # Display miners
506
+ # Display miners (if available)
507
+ miner_ids = file.get("miner_ids", [])
494
508
  miner_count = file.get("miner_count", 0)
495
- miners = file.get("miner_ids", [])
496
509
 
497
- if miner_count > 0:
498
- print(f" Pinned by {miner_count} miners:")
499
-
500
- # Show message about truncated list if applicable
501
- if miner_count > len(miners) and not show_all_miners:
502
- print(
503
- f" (Showing {len(miners)} of {miner_count} miners - use --all-miners to see all)"
510
+ if miner_ids and show_all_miners:
511
+ print(f" Stored by {len(miner_ids)} miners:")
512
+ for miner in miner_ids:
513
+ miner_id = (
514
+ miner.get("id", miner) if isinstance(miner, dict) else miner
504
515
  )
505
- elif miner_count > 3 and show_all_miners:
506
- print(f" (Showing all {miner_count} miners)")
507
-
508
- # Display the miners using their formatted IDs
509
- for miner in miners:
510
- if isinstance(miner, dict) and "formatted" in miner:
511
- print(f" - {miner['formatted']}")
512
- else:
513
- print(f" - {miner}")
516
+ formatted = (
517
+ miner.get("formatted", miner_id)
518
+ if isinstance(miner, dict)
519
+ else miner_id
520
+ )
521
+ print(f" - {formatted}")
522
+ elif miner_count:
523
+ print(f" Stored by {miner_count} miners")
514
524
  else:
515
- print(" Not pinned by any miners")
525
+ print(f" Storage information not available")
516
526
 
517
527
  print("-" * 80)
518
- else:
519
- print(
520
- f"No files found for account: {account_address or client.substrate_client._keypair.ss58_address}"
521
- )
528
+ except Exception as e:
529
+ print(f" Error displaying file {i}: {e}")
530
+ print("-" * 80)
531
+ continue
532
+
533
+ # Add tip for downloading
534
+ if files:
535
+ print("\nTo download a file, use:")
536
+ print(f" hippius download <CID> <output_filename>")
537
+
522
538
  except Exception as e:
523
- print(f"Error retrieving file information: {e}")
539
+ print(f"Error retrieving files: {e}")
524
540
  return 1
525
541
 
526
542
  return 0
527
543
 
528
544
 
529
545
  def handle_ec_files(client, account_address, show_all_miners=False, show_chunks=False):
530
- """
531
- Display erasure-coded files stored by a user.
532
-
533
- This command only reads data and doesn't require seed phrase decryption.
534
- """
535
- # For progress reporting
536
- processed_files = 0
537
- total_files = 0
538
- lock = threading.Lock()
539
-
540
- # Store results from worker threads
541
- results = {
542
- "ec_files": [],
543
- "binary_files": 0,
544
- "json_decode_errors": 0,
545
- "not_ec_files": 0,
546
- "skipped_files": 0,
547
- }
548
-
549
- # For quick identification of potential EC files - common naming patterns
550
- EC_FILENAME_PATTERNS = ["metadata", "ec-", "erasure"]
551
-
552
- # Debug print function that can be enabled/disabled
553
- verbose = get_config_value("cli", "verbose", False)
554
-
555
- def debug_print(msg):
556
- if verbose:
557
- print(msg)
558
-
546
+ """Handle the ec-files command to show only erasure-coded files"""
547
+ print("Looking for erasure-coded files...")
559
548
  try:
560
549
  # Get the account address we're querying
561
550
  if account_address is None:
@@ -579,251 +568,174 @@ def handle_ec_files(client, account_address, show_all_miners=False, show_chunks=
579
568
  )
580
569
  return 1
581
570
 
582
- # Get all files for the account
583
- print(f"Fetching files for account: {account_address}")
584
- files = client.substrate_client.get_user_files(
585
- account_address=account_address,
586
- truncate_miners=not show_all_miners,
587
- max_miners=0 if show_all_miners else 3,
588
- )
571
+ # First, get all user files using the profile method
572
+ files = client.substrate_client.get_user_files_from_profile(account_address)
589
573
 
590
- if not files:
591
- print(f"No files found for account: {account_address}")
592
- return
593
-
594
- total_files = len(files)
595
- print(f"Found {total_files} files, analyzing for erasure-coded metadata...")
596
-
597
- # First, do a quick initial filter to identify potential EC files based on name patterns
598
- potential_ec_files = []
574
+ # Filter for metadata files (ending with .ec_metadata)
575
+ ec_metadata_files = []
599
576
  for file in files:
600
- cid = file.get("file_hash")
601
- if not cid:
602
- results["skipped_files"] += 1
603
- continue
577
+ file_name = file.get("file_name", "")
578
+ if (
579
+ file_name
580
+ and isinstance(file_name, str)
581
+ and file_name.endswith(".ec_metadata")
582
+ ):
583
+ ec_metadata_files.append(file)
604
584
 
605
- # Check if filename contains any of our EC patterns
606
- name = file.get("file_name", "").lower()
607
- if any(pattern in name for pattern in EC_FILENAME_PATTERNS):
608
- # Higher chance this is an EC file
609
- debug_print(f" - Potential EC file based on name: {name}")
610
- potential_ec_files.append((0, file)) # Priority 0 = high
611
- else:
612
- # Still check it, but with lower priority
613
- potential_ec_files.append((1, file)) # Priority 1 = lower
614
-
615
- # Sort by priority (check likely EC files first)
616
- potential_ec_files.sort(key=lambda x: x[0])
617
- priority_files = [f for _, f in potential_ec_files]
618
-
619
- # Progress update function
620
- def update_progress():
621
- nonlocal processed_files
622
- processed_files += 1
623
- if processed_files % 5 == 0 or processed_files == total_files:
624
- print(
625
- f" Progress: {processed_files}/{total_files} files analyzed ({(processed_files/total_files)*100:.1f}%)",
626
- end="\r",
627
- )
585
+ if not ec_metadata_files:
586
+ print(f"No erasure-coded files found for account {account_address}")
587
+ return 0
628
588
 
629
- # Function to process a single file - for parallel execution
630
- def process_file(file):
631
- try:
632
- cid = file.get("file_hash")
633
- name = file.get("file_name", "")
589
+ print(f"\nFound {len(ec_metadata_files)} erasure-coded files:")
590
+ print("-" * 80)
634
591
 
635
- debug_print(f" - Processing: {cid} ({name})")
592
+ for i, file in enumerate(ec_metadata_files, 1):
593
+ try:
594
+ print(f"EC File {i}:")
636
595
 
637
- # Try to fetch metadata to see if it's an erasure-coded file
638
- metadata = client.ipfs_client.cat(cid)
639
- if not metadata or not metadata.get("content"):
640
- with lock:
641
- results["skipped_files"] += 1
642
- update_progress()
643
- return None
596
+ # Get the metadata CID
597
+ metadata_cid = file.get("file_hash", "Unknown")
598
+ if metadata_cid is not None and metadata_cid != "Unknown":
599
+ formatted_cid = client.format_cid(metadata_cid)
600
+ print(f" Metadata CID: {formatted_cid}")
644
601
 
645
- content = metadata.get("content")
646
- if isinstance(content, bytes):
602
+ # Fetch and parse the metadata to get original file info
647
603
  try:
648
- # Try to decode the content as UTF-8 text - might fail for binary files
649
- metadata_text = content.decode("utf-8", errors="strict")
650
-
651
- try:
652
- metadata_obj = json.loads(metadata_text)
653
-
654
- # Check if this is an erasure-coded file metadata - look for either format
655
- is_ec_file = False
656
-
657
- # Check primary format
658
- if (
659
- isinstance(metadata_obj, dict)
660
- and metadata_obj.get("chunks")
661
- and metadata_obj.get("original_name")
662
- ):
663
- is_ec_file = True
664
-
665
- # Check alternative format - different structure used in some versions
666
- elif (
667
- isinstance(metadata_obj, dict)
668
- and metadata_obj.get("erasure_coding")
669
- and metadata_obj.get("original_file")
670
- ):
671
- # This is the newer format with a different structure
672
- metadata_obj = {
673
- "original_name": metadata_obj.get(
674
- "original_file", {}
675
- ).get("name", "unknown"),
676
- "k": metadata_obj.get("erasure_coding", {}).get(
677
- "k"
678
- ),
679
- "m": metadata_obj.get("erasure_coding", {}).get(
680
- "m"
681
- ),
682
- "original_size": metadata_obj.get(
683
- "original_file", {}
684
- ).get("size", 0),
685
- "encrypted": metadata_obj.get("encrypted", False),
686
- "chunks": metadata_obj.get("chunks", []),
687
- }
688
- is_ec_file = True
689
-
690
- if is_ec_file:
691
- # Found an erasure-coded file!
692
- debug_print(
693
- f" ✓ Found erasure-coded file: {metadata_obj.get('original_name')}"
604
+ # Use the formatted CID, not the raw hex-encoded version
605
+ metadata = client.ipfs_client.cat(formatted_cid)
606
+
607
+ # Check if we have text content
608
+ if metadata.get("is_text", False):
609
+ # Parse the metadata content as JSON
610
+ import json
611
+
612
+ metadata_json = json.loads(metadata.get("content", "{}"))
613
+
614
+ # Extract original file info
615
+ # Check both possible formats
616
+ original_file = metadata_json.get("original_file", {})
617
+
618
+ if original_file:
619
+ # New format
620
+ print(
621
+ f" Original file name: {original_file.get('name', 'Unknown')}"
694
622
  )
695
623
 
696
- ec_file = {
697
- "metadata_cid": cid,
698
- "original_name": metadata_obj.get(
699
- "original_name", "unknown"
700
- ),
701
- "k": metadata_obj.get("k"),
702
- "m": metadata_obj.get("m"),
703
- "total_chunks": len(metadata_obj.get("chunks", [])),
704
- "original_size_bytes": metadata_obj.get(
705
- "original_size"
706
- ),
707
- "size_formatted": client.format_size(
708
- metadata_obj.get("original_size", 0)
709
- ),
710
- "encrypted": metadata_obj.get("encrypted", False),
711
- "miner_ids": file.get("miner_ids", []),
712
- "miner_count": file.get("miner_count", 0),
713
- "chunks": metadata_obj.get("chunks", []),
714
- }
715
- with lock:
716
- results["ec_files"].append(ec_file)
717
- update_progress()
718
- return ec_file
624
+ # Show file size
625
+ original_size = original_file.get("size", 0)
626
+ if original_size:
627
+ size_formatted = client.format_size(original_size)
628
+ print(
629
+ f" Original file size: {original_size:,} bytes ({size_formatted})"
630
+ )
631
+ else:
632
+ print(f" Original file size: Unknown")
633
+
634
+ # Show hash/CID of original file if available
635
+ original_hash = original_file.get("hash", "")
636
+ if original_hash:
637
+ print(f" Original file hash: {original_hash}")
638
+
639
+ # Show extension if available
640
+ extension = original_file.get("extension", "")
641
+ if extension:
642
+ print(f" File extension: {extension}")
643
+ else:
644
+ # Try older format
645
+ original_name = metadata_json.get(
646
+ "original_name", "Unknown"
647
+ )
648
+ print(f" Original file name: {original_name}")
649
+
650
+ original_size = metadata_json.get("original_size", 0)
651
+ if original_size:
652
+ size_formatted = client.format_size(original_size)
653
+ print(
654
+ f" Original file size: {original_size:,} bytes ({size_formatted})"
655
+ )
656
+ else:
657
+ print(f" Original file size: Unknown")
658
+
659
+ # Show erasure coding parameters if available
660
+ ec_params = metadata_json.get("erasure_coding", {})
661
+ if ec_params:
662
+ k = ec_params.get("k", 0)
663
+ m = ec_params.get("m", 0)
664
+ if k and m:
665
+ print(
666
+ f" Erasure coding: k={k}, m={m} (need {k} of {k+m} parts)"
667
+ )
719
668
  else:
720
- with lock:
721
- results["not_ec_files"] += 1
722
- update_progress()
723
- return None
724
-
725
- except json.JSONDecodeError:
726
- # Not a JSON file, so not metadata
727
- with lock:
728
- results["json_decode_errors"] += 1
729
- update_progress()
730
- return None
731
- except UnicodeDecodeError:
732
- # This is a binary file, not UTF-8 text, so not erasure-coded metadata
733
- with lock:
734
- results["binary_files"] += 1
735
- update_progress()
736
- return None
669
+ # Check old format
670
+ k = metadata_json.get("k", 0)
671
+ m = metadata_json.get("m", 0)
672
+ if k and m:
673
+ print(
674
+ f" Erasure coding: k={k}, m={m} (need {k} of {k+m} parts)"
675
+ )
676
+
677
+ # Show encryption status if available
678
+ encrypted = metadata_json.get("encrypted", False)
679
+ print(f" Encrypted: {'Yes' if encrypted else 'No'}")
680
+
681
+ # Count chunks
682
+ chunks = metadata_json.get("chunks", [])
683
+ if chunks:
684
+ print(f" Total chunks: {len(chunks)}")
685
+
686
+ # Show chunk details if requested
687
+ if show_chunks:
688
+ print(f" Chunks:")
689
+ for j, chunk in enumerate(chunks):
690
+ chunk_cid = (
691
+ chunk
692
+ if isinstance(chunk, str)
693
+ else chunk.get("cid", "Unknown")
694
+ )
695
+ print(f" Chunk {j+1}: {chunk_cid}")
696
+ else:
697
+ # Couldn't parse metadata as text
698
+ print(f" Error: Metadata is not in text format")
699
+ except Exception as e:
700
+ print(f" Error fetching metadata: {e}")
737
701
  else:
738
- with lock:
739
- results["skipped_files"] += 1
740
- update_progress()
741
- return None
742
- except Exception as e:
743
- # For other unexpected errors
744
- debug_print(f" ✗ Error processing {name}: {str(e)}")
745
- with lock:
746
- results["skipped_files"] += 1
747
- update_progress()
748
- return None
749
-
750
- # Process files in parallel - significantly speeds up execution
751
- max_workers = min(10, len(files)) # Don't create too many threads
752
- print(f"Processing files using {max_workers} parallel workers...")
753
-
754
- with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
755
- # Start processing files - higher priority files will be submitted first
756
- futures = []
757
- # First process files that look like metadata files
758
- for file in priority_files:
759
- futures.append(executor.submit(process_file, file))
760
-
761
- # Wait for all processing to complete
762
- concurrent.futures.wait(futures)
702
+ print(f" Metadata CID: Unknown (None)")
763
703
 
764
- print(
765
- "\nAnalysis complete! "
766
- ) # Extra spaces to clear progress line
704
+ # Display file name (metadata file name)
705
+ file_name = file.get("file_name", "Unnamed")
706
+ print(
707
+ f" Metadata file name: {file_name if file_name is not None else 'Unnamed'}"
708
+ )
767
709
 
768
- # Print statistics
769
- print(f"\nResults:")
770
- print(f" Total files analyzed: {total_files}")
771
- print(f" Erasure-coded files found: {len(results['ec_files'])}")
772
- if verbose:
773
- print(f" Binary files (skipped): {results['binary_files']}")
774
- print(f" JSON parse errors: {results['json_decode_errors']}")
775
- print(f" Not erasure-coded: {results['not_ec_files']}")
776
- print(f" Other skipped: {results['skipped_files']}")
777
-
778
- # Print results
779
- ec_files = results["ec_files"]
780
- if not ec_files:
781
- print(f"\nNo erasure-coded files found for this account.")
782
- return
783
-
784
- print(f"\nFound {len(ec_files)} erasure-coded files:\n")
785
-
786
- for i, file in enumerate(ec_files):
787
- print(f"{i+1}. {file['original_name']} ({file['size_formatted']})")
788
- print(f" Metadata CID: {file['metadata_cid']}")
789
- print(
790
- f" Erasure coding: {file['k']}/{file['m']} scheme ({file['total_chunks']} chunks)"
791
- )
792
- print(f" Encrypted: {'Yes' if file['encrypted'] else 'No'}")
793
- print(f" Stored by {file['miner_count']} miners")
794
-
795
- if file.get("miner_ids") and show_all_miners:
796
- print("\n Miners:")
797
- for miner in file["miner_ids"]:
798
- miner_id = (
799
- miner.get("id", miner) if isinstance(miner, dict) else miner
710
+ # Show reconstruction command
711
+ if metadata_cid is not None and metadata_cid != "Unknown":
712
+ print(f" Reconstruction command:")
713
+ # Try to extract original name from metadata file name
714
+ original_name = (
715
+ file_name.replace(".ec_metadata", "") if file_name else "file"
800
716
  )
801
- formatted = (
802
- miner.get("formatted", miner_id)
803
- if isinstance(miner, dict)
804
- else miner_id
717
+ # We're already using formatted_cid from above
718
+ print(
719
+ f" hippius reconstruct {formatted_cid} reconstructed_{original_name}"
805
720
  )
806
- print(f" - {formatted}")
807
-
808
- if show_chunks and file.get("chunks"):
809
- print("\n Chunks:")
810
- for j, chunk in enumerate(file["chunks"]):
811
- if isinstance(chunk, dict):
812
- print(f" {j+1}. CID: {chunk.get('cid')}")
813
- else:
814
- print(f" {j+1}. CID: {chunk}")
721
+ else:
722
+ print(f" Reconstruction command not available (missing CID)")
815
723
 
816
- print("") # Empty line between files
724
+ print("-" * 80)
725
+ except Exception as e:
726
+ print(f" Error displaying EC file {i}: {e}")
727
+ print("-" * 80)
728
+ continue
817
729
 
818
- print("\nTo reconstruct a file:")
819
- print("hippius reconstruct <metadata_cid> <output_file>")
730
+ # Add helpful tips
731
+ print("\nTo reconstruct a file, use:")
732
+ print(f" hippius reconstruct <Metadata_CID> <output_filename>")
820
733
 
821
734
  except Exception as e:
822
- print(f"Error: {e}")
823
- if verbose:
824
- import traceback
735
+ print(f"Error retrieving erasure-coded files: {e}")
736
+ return 1
825
737
 
826
- traceback.print_exc()
738
+ return 0
827
739
 
828
740
 
829
741
  def handle_erasure_code(
@@ -977,8 +889,11 @@ def handle_erasure_code(
977
889
  print(f" 1. The metadata CID: {metadata_cid}")
978
890
  print(" 2. Access to at least k chunks for each original chunk")
979
891
  print("\nReconstruction command:")
892
+
893
+ # Format the CID for the command
894
+ formatted_cid = client.format_cid(metadata_cid)
980
895
  print(
981
- f" hippius reconstruct {metadata_cid} reconstructed_{original_file.get('name')}"
896
+ f" hippius reconstruct {formatted_cid} reconstructed_{original_file.get('name')}"
982
897
  )
983
898
 
984
899
  return 0
@@ -1165,9 +1080,12 @@ def handle_reconstruct(client, metadata_cid, output_file, verbose=True):
1165
1080
  start_time = time.time()
1166
1081
 
1167
1082
  try:
1168
- # Use the reconstruct_from_erasure_code method
1083
+ # Format the CID to ensure it's properly handled
1084
+ formatted_cid = client.format_cid(metadata_cid)
1085
+
1086
+ # Use the formatted CID for reconstruction
1169
1087
  result = client.reconstruct_from_erasure_code(
1170
- metadata_cid=metadata_cid, output_file=output_file, verbose=verbose
1088
+ metadata_cid=formatted_cid, output_file=output_file, verbose=verbose
1171
1089
  )
1172
1090
 
1173
1091
  elapsed_time = time.time() - start_time
@@ -1439,125 +1357,316 @@ def handle_seed_phrase_status(account_name=None):
1439
1357
  return 0
1440
1358
 
1441
1359
 
1442
- def handle_account_list():
1443
- """Handle listing all accounts"""
1444
- accounts = list_accounts()
1360
+ def handle_account_list(args):
1361
+ """Handle listing accounts."""
1362
+ from hippius_sdk.account import AccountManager
1445
1363
 
1446
- if not accounts:
1447
- print("No accounts configured")
1448
- return 0
1364
+ account_manager = AccountManager()
1449
1365
 
1450
- print(f"Found {len(accounts)} accounts:")
1366
+ if args.type == "coldkey":
1367
+ accounts = account_manager.list_coldkeys()
1368
+ print(f"Found {len(accounts)} coldkeys:")
1451
1369
 
1452
- for name, data in accounts.items():
1453
- active_marker = " (active)" if data.get("is_active", False) else ""
1454
- encoded_status = (
1455
- "encrypted" if data.get("seed_phrase_encoded", False) else "plain text"
1456
- )
1457
- address = data.get("ss58_address", "unknown")
1370
+ for i, account in enumerate(accounts, 1):
1371
+ print(f"{i}. {account['name']}: {account['address']}")
1458
1372
 
1459
- print(f" {name}{active_marker}:")
1460
- print(f" SS58 Address: {address}")
1461
- print(f" Seed phrase: {encoded_status}")
1462
- print()
1373
+ # If verbose, show associated hotkeys
1374
+ if args.verbose:
1375
+ hotkeys = account_manager.list_hotkeys(account["address"])
1376
+ if hotkeys:
1377
+ print(f" Associated hotkeys:")
1378
+ for j, hotkey in enumerate(hotkeys, 1):
1379
+ print(f" {j}. {hotkey['name']}: {hotkey['address']}")
1380
+ else:
1381
+ print(f" No associated hotkeys")
1463
1382
 
1464
- return 0
1383
+ elif args.type == "hotkey":
1384
+ try:
1385
+ if args.coldkey:
1386
+ accounts = account_manager.list_hotkeys(args.coldkey)
1387
+ coldkey_name = None
1388
+ if accounts and "coldkey_name" in accounts[0]:
1389
+ coldkey_name = accounts[0]["coldkey_name"]
1465
1390
 
1391
+ if coldkey_name:
1392
+ print(
1393
+ f"Found {len(accounts)} hotkeys for coldkey {args.coldkey} ({coldkey_name}):"
1394
+ )
1395
+ else:
1396
+ print(f"Found {len(accounts)} hotkeys for coldkey {args.coldkey}:")
1397
+ else:
1398
+ accounts = account_manager.list_hotkeys()
1399
+ print(f"Found {len(accounts)} hotkeys:")
1400
+
1401
+ for i, account in enumerate(accounts, 1):
1402
+ coldkey_info = ""
1403
+ if "associated_coldkey" in account:
1404
+ if "coldkey_name" in account:
1405
+ coldkey_info = f" → coldkey: {account['coldkey_name']} ({account['associated_coldkey']})"
1406
+ else:
1407
+ coldkey_info = f" → coldkey: {account['associated_coldkey']}"
1466
1408
 
1467
- def handle_account_switch(account_name):
1468
- """Handle switching the active account"""
1469
- if set_active_account(account_name):
1470
- print(f"Switched to account '{account_name}'")
1409
+ print(f"{i}. {account['name']}: {account['address']}{coldkey_info}")
1471
1410
 
1472
- # Show address
1473
- address = get_account_address(account_name)
1474
- if address:
1475
- print(f"SS58 Address: {address}")
1411
+ # If verbose, show more details
1412
+ if args.verbose:
1413
+ created_at = account.get("created_at", 0)
1414
+ if created_at:
1415
+ from datetime import datetime
1476
1416
 
1477
- return 0
1478
- else:
1479
- return 1
1417
+ created_time = datetime.fromtimestamp(created_at).strftime(
1418
+ "%Y-%m-%d %H:%M:%S"
1419
+ )
1420
+ print(f" Created: {created_time}")
1480
1421
 
1422
+ # Show whether there is a blockchain proxy relationship
1423
+ try:
1424
+ proxies = account_manager.list_proxies(
1425
+ account.get("associated_coldkey")
1426
+ )
1427
+ has_proxy = any(
1428
+ proxy["hotkey"] == account["address"] for proxy in proxies
1429
+ )
1430
+ if has_proxy:
1431
+ print(f" Blockchain proxy: YES (active on-chain)")
1432
+ else:
1433
+ print(f" Blockchain proxy: NO (local association only)")
1434
+ except Exception:
1435
+ print(f" Blockchain proxy: Unknown (could not verify)")
1481
1436
 
1482
- def handle_account_delete(account_name):
1483
- """Handle deleting an account"""
1484
- # Ask for confirmation
1485
- confirm = input(
1486
- f"Are you sure you want to delete account '{account_name}'? This cannot be undone. (y/N): "
1487
- )
1488
- if confirm.lower() not in ("y", "yes"):
1489
- print("Operation cancelled")
1490
- return 0
1437
+ except ValueError as e:
1438
+ print(f"Error: {str(e)}")
1439
+ return None
1440
+ except Exception as e:
1441
+ print(f"Error listing hotkeys: {str(e)}")
1442
+ return None
1443
+
1444
+ elif args.type == "proxy":
1445
+ try:
1446
+ proxies = account_manager.list_proxies(args.coldkey)
1447
+ print(f"Found {len(proxies)} proxy relationships on the blockchain:")
1448
+
1449
+ for i, proxy in enumerate(proxies, 1):
1450
+ coldkey = proxy["coldkey"]
1451
+ hotkey = proxy["hotkey"]
1452
+
1453
+ # Try to get human-readable names
1454
+ data = account_manager._load_accounts_data()
1455
+ coldkey_name = (
1456
+ data.get("coldkeys", {}).get(coldkey, {}).get("name", "Unknown")
1457
+ )
1458
+ hotkey_name = (
1459
+ data.get("hotkeys", {}).get(hotkey, {}).get("name", "Unknown")
1460
+ )
1461
+
1462
+ print(f"{i}. {coldkey_name} ({coldkey}) → {hotkey_name} ({hotkey})")
1463
+ print(f" Type: {proxy['proxy_type']}, Delay: {proxy['delay']}")
1464
+ except Exception as e:
1465
+ print(f"Error listing proxies: {str(e)}")
1466
+
1467
+ return accounts
1468
+
1469
+
1470
+ def handle_account_proxy_create(args):
1471
+ """Handle creating a proxy relationship."""
1472
+ from hippius_sdk.account import AccountManager
1473
+
1474
+ account_manager = AccountManager()
1491
1475
 
1492
- if delete_account(account_name):
1493
- print(f"Account '{account_name}' deleted")
1476
+ try:
1477
+ result = account_manager.create_proxy_relationship(
1478
+ coldkey_address=args.coldkey,
1479
+ hotkey_address=args.hotkey,
1480
+ proxy_type=args.proxy_type,
1481
+ delay=args.delay,
1482
+ password=args.password, # Pass the password if provided
1483
+ )
1494
1484
 
1495
- # Show the new active account if any
1496
- active_account = get_active_account()
1497
- if active_account:
1498
- print(f"Active account is now '{active_account}'")
1485
+ if result.get("success"):
1486
+ print(f"Successfully created proxy relationship!")
1487
+ print(f"Coldkey: {result['coldkey']}")
1488
+ print(f"Hotkey: {result['hotkey']}")
1489
+ print(f"Type: {result['proxy_type']}")
1490
+ print(f"Tx Hash: {result['transaction_hash']}")
1499
1491
  else:
1500
- print("No accounts remaining")
1492
+ print(
1493
+ f"Failed to create proxy relationship: {result.get('error', 'Unknown error')}"
1494
+ )
1501
1495
 
1502
- return 0
1503
- else:
1504
- return 1
1496
+ return result
1497
+
1498
+ except Exception as e:
1499
+ print(f"Error creating proxy relationship: {str(e)}")
1500
+ return None
1505
1501
 
1506
1502
 
1507
- def handle_default_address_set(address):
1508
- """Handle setting the default address for read-only operations"""
1509
- # Validate SS58 address format (basic check)
1510
- if not address.startswith("5"):
1511
- print(
1512
- f"Warning: '{address}' doesn't look like a valid SS58 address. SS58 addresses typically start with '5'."
1503
+ def handle_account_proxy_remove(args):
1504
+ """Handle removing a proxy relationship."""
1505
+ from hippius_sdk.account import AccountManager
1506
+
1507
+ account_manager = AccountManager()
1508
+
1509
+ try:
1510
+ result = account_manager.remove_proxy(
1511
+ coldkey_address=args.coldkey,
1512
+ hotkey_address=args.hotkey,
1513
+ password=args.password,
1514
+ proxy_type=args.proxy_type,
1515
+ delay=args.delay,
1513
1516
  )
1514
- confirm = input("Do you want to continue anyway? (y/N): ")
1515
- if confirm.lower() not in ("y", "yes"):
1516
- print("Operation cancelled")
1517
- return 1
1518
1517
 
1519
- config = load_config()
1520
- config["substrate"]["default_address"] = address
1521
- save_config(config)
1518
+ if result.get("success"):
1519
+ print(f"Successfully removed proxy relationship!")
1520
+ print(f"Coldkey: {result['coldkey']}")
1521
+ print(f"Hotkey: {result['hotkey']}")
1522
+ print(f"Tx Hash: {result['transaction_hash']}")
1523
+ else:
1524
+ print(
1525
+ f"Failed to remove proxy relationship: {result.get('error', 'Unknown error')}"
1526
+ )
1522
1527
 
1523
- print(f"Default address for read-only operations set to: {address}")
1524
- print(
1525
- "This address will be used for commands like 'files' and 'ec-files' when no address is explicitly provided."
1526
- )
1527
- return 0
1528
+ return result
1528
1529
 
1530
+ except Exception as e:
1531
+ print(f"Error removing proxy relationship: {str(e)}")
1532
+ return None
1529
1533
 
1530
- def handle_default_address_get():
1531
- """Handle getting the current default address for read-only operations"""
1532
- config = load_config()
1533
- address = config["substrate"].get("default_address")
1534
1534
 
1535
- if address:
1536
- print(f"Current default address for read-only operations: {address}")
1537
- else:
1538
- print("No default address set for read-only operations")
1539
- print("You can set one with: hippius address set-default <ss58_address>")
1535
+ def handle_account_coldkey_create(args):
1536
+ """Handle creating a new coldkey account."""
1537
+ from hippius_sdk.account import AccountManager
1540
1538
 
1541
- return 0
1539
+ account_manager = AccountManager()
1542
1540
 
1541
+ # Determine if we're creating from a mnemonic
1542
+ mnemonic = None
1543
+ if args.mnemonic:
1544
+ mnemonic = args.mnemonic
1545
+ elif args.generate_mnemonic:
1546
+ # We'll use the keypair generation which automatically creates a mnemonic
1547
+ pass
1543
1548
 
1544
- def handle_default_address_clear():
1545
- """Handle clearing the default address for read-only operations"""
1546
- config = load_config()
1547
- if "default_address" in config["substrate"]:
1548
- del config["substrate"]["default_address"]
1549
- save_config(config)
1550
- print("Default address for read-only operations has been cleared")
1551
- else:
1552
- print("No default address was set")
1549
+ try:
1550
+ coldkey = account_manager.create_coldkey(
1551
+ name=args.name if args.name else "hippius_coldkey",
1552
+ mnemonic=mnemonic,
1553
+ encrypt=not args.no_encrypt, # Default to encrypt=True unless explicitly disabled
1554
+ password=None, # Let the method handle prompting for password
1555
+ )
1553
1556
 
1554
- return 0
1557
+ # Success message is already printed in the create_coldkey method
1558
+ # If show_mnemonic was requested and it's not encrypted, show it
1559
+ if args.show_mnemonic and not coldkey.get("encrypted", True):
1560
+ print("\nIMPORTANT: Save this mnemonic phrase to recover your account!")
1561
+ print(f"Mnemonic: {coldkey['mnemonic']}")
1562
+ print("\nWARNING: Never share this mnemonic with anyone!")
1555
1563
 
1564
+ return coldkey
1565
+
1566
+ except Exception as e:
1567
+ print(f"Error creating coldkey: {str(e)}")
1568
+ return None
1569
+
1570
+
1571
+ def handle_account_hotkey_create(args):
1572
+ """Handle creating a new hotkey account."""
1573
+ from hippius_sdk.account import AccountManager
1574
+
1575
+ account_manager = AccountManager()
1576
+
1577
+ try:
1578
+ # If no coldkey provided, let the AccountManager handle it
1579
+ # It will use the only coldkey if there's just one, or raise an error
1580
+ hotkey = account_manager.create_hotkey(
1581
+ name=args.name, coldkey_address=args.coldkey
1582
+ )
1583
+
1584
+ # Success message is already printed in create_hotkey
1585
+
1586
+ # If show_mnemonic was requested, show it
1587
+ if args.show_mnemonic and hotkey.get("mnemonic"):
1588
+ print("\nHotkey mnemonic: {0}".format(hotkey["mnemonic"]))
1589
+ print("Note: This mnemonic is only needed if you want to use this hotkey")
1590
+ print(" outside of the Hippius SDK. For normal operations, the proxy")
1591
+ print(" relationship will allow your coldkey to authorize actions.")
1592
+
1593
+ return hotkey
1594
+
1595
+ except ValueError as e:
1596
+ # Handle the specific error case of multiple coldkeys
1597
+ if "multiple coldkeys exist" in str(e):
1598
+ print(f"Error: {e}")
1599
+ print("\nPlease specify which coldkey to use with:")
1600
+ print(
1601
+ " hippius account hotkey create --coldkey <COLDKEY_ADDRESS> [--name <NAME>]"
1602
+ )
1603
+ else:
1604
+ print(f"Error creating hotkey: {str(e)}")
1605
+
1606
+ return None
1607
+ except Exception as e:
1608
+ print(f"Error creating hotkey: {str(e)}")
1609
+ return None
1556
1610
 
1557
- def get_default_address():
1558
- """Get the default address for read-only operations"""
1559
- config = load_config()
1560
- return config["substrate"].get("default_address")
1611
+
1612
+ def handle_account_proxy_create(args):
1613
+ """Handle creating a proxy relationship."""
1614
+ from hippius_sdk.account import AccountManager
1615
+
1616
+ account_manager = AccountManager()
1617
+
1618
+ try:
1619
+ result = account_manager.create_proxy_relationship(
1620
+ coldkey_address=args.coldkey,
1621
+ hotkey_address=args.hotkey,
1622
+ proxy_type=args.proxy_type,
1623
+ delay=args.delay,
1624
+ )
1625
+
1626
+ if result.get("success"):
1627
+ print(f"Successfully created proxy relationship!")
1628
+ print(f"Coldkey: {result['coldkey']}")
1629
+ print(f"Hotkey: {result['hotkey']}")
1630
+ print(f"Type: {result['proxy_type']}")
1631
+ print(f"Tx Hash: {result['transaction_hash']}")
1632
+ else:
1633
+ print(
1634
+ f"Failed to create proxy relationship: {result.get('error', 'Unknown error')}"
1635
+ )
1636
+
1637
+ return result
1638
+
1639
+ except Exception as e:
1640
+ print(f"Error creating proxy relationship: {str(e)}")
1641
+ return None
1642
+
1643
+
1644
+ def handle_account_proxy_remove(args):
1645
+ """Handle removing a proxy relationship."""
1646
+ from hippius_sdk.account import AccountManager
1647
+
1648
+ account_manager = AccountManager()
1649
+
1650
+ try:
1651
+ result = account_manager.remove_proxy(
1652
+ coldkey_address=args.coldkey, hotkey_address=args.hotkey
1653
+ )
1654
+
1655
+ if result.get("success"):
1656
+ print(f"Successfully removed proxy relationship!")
1657
+ print(f"Coldkey: {result['coldkey']}")
1658
+ print(f"Hotkey: {result['hotkey']}")
1659
+ print(f"Tx Hash: {result['transaction_hash']}")
1660
+ else:
1661
+ print(
1662
+ f"Failed to remove proxy relationship: {result.get('error', 'Unknown error')}"
1663
+ )
1664
+
1665
+ return result
1666
+
1667
+ except Exception as e:
1668
+ print(f"Error removing proxy relationship: {str(e)}")
1669
+ return None
1561
1670
 
1562
1671
 
1563
1672
  def main():
@@ -1719,42 +1828,50 @@ examples:
1719
1828
 
1720
1829
  # Files command
1721
1830
  files_parser = subparsers.add_parser(
1722
- "files", help="View detailed information about files stored by a user"
1723
- )
1724
- files_parser.add_argument(
1725
- "account_address",
1726
- nargs="?",
1727
- default=None,
1728
- help="Substrate account address (uses keypair address if not specified)",
1831
+ "files", help="View files stored by you or another account"
1729
1832
  )
1730
1833
  files_parser.add_argument(
1731
- "--debug", action="store_true", help="Show debug information about CID decoding"
1834
+ "--account_address",
1835
+ help="Substrate account to view files for (defaults to your keyfile account)",
1732
1836
  )
1733
1837
  files_parser.add_argument(
1734
1838
  "--all-miners",
1735
1839
  action="store_true",
1736
- help="Show all miners for each file instead of only the first 3",
1840
+ help="Show all miners for each file",
1841
+ )
1842
+ files_parser.set_defaults(
1843
+ func=lambda args, client: handle_files(
1844
+ client,
1845
+ args.account_address,
1846
+ show_all_miners=args.all_miners if hasattr(args, "all_miners") else False,
1847
+ )
1737
1848
  )
1738
1849
 
1739
1850
  # Erasure Coded Files command
1740
1851
  ec_files_parser = subparsers.add_parser(
1741
- "ec-files", help="List only erasure-coded files stored by a user"
1852
+ "ec-files", help="View erasure-coded files stored by you or another account"
1742
1853
  )
1743
1854
  ec_files_parser.add_argument(
1744
- "account_address",
1745
- nargs="?",
1746
- default=None,
1747
- help="Substrate account address (uses keypair address if not specified)",
1855
+ "--account_address",
1856
+ help="Substrate account to view erasure-coded files for (defaults to your keyfile account)",
1748
1857
  )
1749
1858
  ec_files_parser.add_argument(
1750
1859
  "--all-miners",
1751
1860
  action="store_true",
1752
- help="Show all miners for each file instead of only the first 3",
1861
+ help="Show all miners for each file",
1753
1862
  )
1754
1863
  ec_files_parser.add_argument(
1755
1864
  "--show-chunks",
1756
1865
  action="store_true",
1757
- help="Show associated chunks for each erasure-coded file",
1866
+ help="Show chunk details for each erasure-coded file",
1867
+ )
1868
+ ec_files_parser.set_defaults(
1869
+ func=lambda args, client: handle_ec_files(
1870
+ client,
1871
+ args.account_address,
1872
+ show_all_miners=args.all_miners if hasattr(args, "all_miners") else False,
1873
+ show_chunks=args.show_chunks if hasattr(args, "show_chunks") else False,
1874
+ )
1758
1875
  )
1759
1876
 
1760
1877
  # Key generation command
@@ -1798,6 +1915,41 @@ examples:
1798
1915
  "--verbose", action="store_true", help="Enable verbose output", default=True
1799
1916
  )
1800
1917
 
1918
+ # Erasure code directory command
1919
+ erasure_code_dir_parser = subparsers.add_parser(
1920
+ "erasure-code-dir", help="Apply erasure coding to each file in a directory"
1921
+ )
1922
+ erasure_code_dir_parser.add_argument(
1923
+ "dir_path", help="Path to directory to process"
1924
+ )
1925
+ erasure_code_dir_parser.add_argument(
1926
+ "--k",
1927
+ type=int,
1928
+ default=3,
1929
+ help="Number of data chunks needed to reconstruct (default: 3)",
1930
+ )
1931
+ erasure_code_dir_parser.add_argument(
1932
+ "--m", type=int, default=5, help="Total number of chunks to create (default: 5)"
1933
+ )
1934
+ erasure_code_dir_parser.add_argument(
1935
+ "--chunk-size",
1936
+ type=int,
1937
+ default=1048576,
1938
+ help="Chunk size in bytes (default: 1MB)",
1939
+ )
1940
+ erasure_code_dir_parser.add_argument(
1941
+ "--miner-ids", help="Comma-separated list of miner IDs"
1942
+ )
1943
+ erasure_code_dir_parser.add_argument(
1944
+ "--encrypt", action="store_true", help="Encrypt the files"
1945
+ )
1946
+ erasure_code_dir_parser.add_argument(
1947
+ "--no-encrypt", action="store_true", help="Do not encrypt the files"
1948
+ )
1949
+ erasure_code_dir_parser.add_argument(
1950
+ "--verbose", action="store_true", help="Enable verbose output", default=True
1951
+ )
1952
+
1801
1953
  # Reconstruct command
1802
1954
  reconstruct_parser = subparsers.add_parser(
1803
1955
  "reconstruct", help="Reconstruct an erasure-coded file"
@@ -1893,55 +2045,139 @@ examples:
1893
2045
  )
1894
2046
 
1895
2047
  # Account subcommand
1896
- account_parser = subparsers.add_parser("account", help="Manage substrate accounts")
2048
+ account_parser = subparsers.add_parser("account", help="Manage blockchain accounts")
1897
2049
  account_subparsers = account_parser.add_subparsers(
1898
- dest="account_action", help="Account action"
2050
+ dest="account_action", help="Account management actions"
1899
2051
  )
1900
2052
 
1901
- # List accounts
1902
- account_subparsers.add_parser("list", help="List all accounts")
2053
+ # Coldkey subcommands
2054
+ coldkey_parser = account_subparsers.add_parser(
2055
+ "coldkey", help="Manage coldkey accounts"
2056
+ )
2057
+ coldkey_subparsers = coldkey_parser.add_subparsers(
2058
+ dest="coldkey_action", help="Coldkey actions"
2059
+ )
1903
2060
 
1904
- # Switch active account
1905
- switch_account_parser = account_subparsers.add_parser(
1906
- "switch", help="Switch to a different account"
2061
+ # Create coldkey command
2062
+ create_coldkey_parser = coldkey_subparsers.add_parser(
2063
+ "create", help="Create a new coldkey"
1907
2064
  )
1908
- switch_account_parser.add_argument(
1909
- "account_name", help="Name of the account to switch to"
2065
+ create_coldkey_parser.add_argument(
2066
+ "--name", help="Name for the coldkey (default: hippius_coldkey)"
2067
+ )
2068
+ mnemonic_group = create_coldkey_parser.add_mutually_exclusive_group()
2069
+ mnemonic_group.add_argument("--mnemonic", help="Mnemonic seed phrase to use")
2070
+ mnemonic_group.add_argument(
2071
+ "--generate-mnemonic",
2072
+ action="store_true",
2073
+ help="Generate a new mnemonic (default if no mnemonic provided)",
2074
+ )
2075
+ create_coldkey_parser.add_argument(
2076
+ "--show-mnemonic",
2077
+ action="store_true",
2078
+ help="Display the mnemonic after creation (USE WITH CAUTION)",
2079
+ )
2080
+ create_coldkey_parser.add_argument(
2081
+ "--no-encrypt",
2082
+ action="store_true",
2083
+ help="Do NOT encrypt the mnemonic with a password (not recommended)",
2084
+ )
2085
+ create_coldkey_parser.set_defaults(func=handle_account_coldkey_create)
2086
+
2087
+ # Hotkey subcommands
2088
+ hotkey_parser = account_subparsers.add_parser(
2089
+ "hotkey", help="Manage hotkey accounts"
2090
+ )
2091
+ hotkey_subparsers = hotkey_parser.add_subparsers(
2092
+ dest="hotkey_action", help="Hotkey actions"
1910
2093
  )
1911
2094
 
1912
- # Delete account
1913
- delete_account_parser = account_subparsers.add_parser(
1914
- "delete", help="Delete an account"
2095
+ # Create hotkey command
2096
+ create_hotkey_parser = hotkey_subparsers.add_parser(
2097
+ "create", help="Create a new hotkey associated with a coldkey"
2098
+ )
2099
+ create_hotkey_parser.add_argument(
2100
+ "--name", help="Name for the hotkey (default: hippius_hotkey_N)"
2101
+ )
2102
+ create_hotkey_parser.add_argument(
2103
+ "--coldkey",
2104
+ help="Coldkey address to associate with (required if multiple coldkeys exist)",
1915
2105
  )
1916
- delete_account_parser.add_argument(
1917
- "account_name", help="Name of the account to delete"
2106
+ create_hotkey_parser.add_argument(
2107
+ "--show-mnemonic",
2108
+ action="store_true",
2109
+ help="Display the mnemonic after creation",
1918
2110
  )
2111
+ create_hotkey_parser.set_defaults(func=handle_account_hotkey_create)
1919
2112
 
1920
- # Address subcommand for read-only operations
1921
- address_parser = subparsers.add_parser(
1922
- "address", help="Manage default address for read-only operations"
2113
+ # List accounts command
2114
+ list_accounts_parser = account_subparsers.add_parser("list", help="List accounts")
2115
+ list_accounts_parser.add_argument(
2116
+ "type", choices=["coldkey", "hotkey", "proxy"], help="Type of accounts to list"
1923
2117
  )
1924
- address_subparsers = address_parser.add_subparsers(
1925
- dest="address_action", help="Address action"
2118
+ list_accounts_parser.add_argument("--coldkey", help="Coldkey address to filter by")
2119
+ list_accounts_parser.add_argument(
2120
+ "--verbose", "-v", action="store_true", help="Show more details"
1926
2121
  )
2122
+ list_accounts_parser.set_defaults(func=handle_account_list)
1927
2123
 
1928
- # Set default address
1929
- set_default_parser = address_subparsers.add_parser(
1930
- "set-default", help="Set the default address for read-only operations"
2124
+ # Proxy subcommands
2125
+ proxy_parser = account_subparsers.add_parser(
2126
+ "proxy", help="Manage proxy relationships"
1931
2127
  )
1932
- set_default_parser.add_argument(
1933
- "address", help="The SS58 address to use as default"
2128
+ proxy_subparsers = proxy_parser.add_subparsers(
2129
+ dest="proxy_action", help="Proxy actions"
1934
2130
  )
1935
2131
 
1936
- # Get current default address
1937
- address_subparsers.add_parser(
1938
- "get-default", help="Show the current default address for read-only operations"
2132
+ # Create proxy command
2133
+ create_proxy_parser = proxy_subparsers.add_parser(
2134
+ "create", help="Create a proxy relationship"
2135
+ )
2136
+ create_proxy_parser.add_argument(
2137
+ "--coldkey", required=True, help="Coldkey address (delegator)"
2138
+ )
2139
+ create_proxy_parser.add_argument(
2140
+ "--hotkey", required=True, help="Hotkey address (delegate)"
2141
+ )
2142
+ create_proxy_parser.add_argument(
2143
+ "--proxy-type", default="NonTransfer", help="Proxy type (default: NonTransfer)"
2144
+ )
2145
+ create_proxy_parser.add_argument(
2146
+ "--delay",
2147
+ type=int,
2148
+ default=0,
2149
+ help="Delay in blocks before proxy becomes active (default: 0)",
2150
+ )
2151
+ create_proxy_parser.add_argument(
2152
+ "--password",
2153
+ help="Password for the coldkey (will prompt if not provided and needed)",
1939
2154
  )
2155
+ create_proxy_parser.set_defaults(func=handle_account_proxy_create)
1940
2156
 
1941
- # Clear default address
1942
- address_subparsers.add_parser(
1943
- "clear-default", help="Clear the default address for read-only operations"
2157
+ # Remove proxy command
2158
+ remove_proxy_parser = proxy_subparsers.add_parser(
2159
+ "remove", help="Remove a proxy relationship"
1944
2160
  )
2161
+ remove_proxy_parser.add_argument(
2162
+ "--coldkey", required=True, help="Coldkey address (delegator)"
2163
+ )
2164
+ remove_proxy_parser.add_argument(
2165
+ "--hotkey", required=True, help="Hotkey address (delegate)"
2166
+ )
2167
+ remove_proxy_parser.add_argument(
2168
+ "--password",
2169
+ help="Password for the coldkey (will prompt if not provided and needed)",
2170
+ )
2171
+ remove_proxy_parser.add_argument(
2172
+ "--proxy-type", default="NonTransfer", help="Proxy type (default: NonTransfer)"
2173
+ )
2174
+ remove_proxy_parser.add_argument(
2175
+ "--delay",
2176
+ type=int,
2177
+ default=0,
2178
+ help="Delay value used when creating the proxy (default: 0)",
2179
+ )
2180
+ remove_proxy_parser.set_defaults(func=handle_account_proxy_remove)
1945
2181
 
1946
2182
  args = parser.parse_args()
1947
2183
 
@@ -2032,7 +2268,6 @@ examples:
2032
2268
  return handle_files(
2033
2269
  client,
2034
2270
  args.account_address,
2035
- debug=args.debug if hasattr(args, "debug") else False,
2036
2271
  show_all_miners=args.all_miners
2037
2272
  if hasattr(args, "all_miners")
2038
2273
  else False,
@@ -2060,6 +2295,18 @@ examples:
2060
2295
  verbose=args.verbose,
2061
2296
  )
2062
2297
 
2298
+ elif args.command == "erasure-code-dir":
2299
+ return handle_erasure_code_directory(
2300
+ client,
2301
+ args.dir_path,
2302
+ args.k,
2303
+ args.m,
2304
+ args.chunk_size,
2305
+ miner_ids,
2306
+ encrypt=args.encrypt,
2307
+ verbose=args.verbose,
2308
+ )
2309
+
2063
2310
  elif args.command == "reconstruct":
2064
2311
  return handle_reconstruct(
2065
2312
  client, args.metadata_cid, args.output_file, verbose=args.verbose