hippius 0.2.0__py3-none-any.whl → 0.2.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hippius
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Python SDK and CLI for Hippius blockchain storage
5
5
  Home-page: https://github.com/thenervelab/hippius-sdk
6
6
  Author: Dubs
@@ -18,6 +18,7 @@ Provides-Extra: clipboard
18
18
  Requires-Dist: base58 (>=2.1.1,<3.0.0)
19
19
  Requires-Dist: cryptography (>=44.0.0,<45.0.0)
20
20
  Requires-Dist: httpx (>=0.28.1,<0.29.0)
21
+ Requires-Dist: mnemonic (>=0.20,<0.21)
21
22
  Requires-Dist: pydantic (>=2.0.0,<3.0.0)
22
23
  Requires-Dist: pynacl (>=1.5.0,<2.0.0)
23
24
  Requires-Dist: pyperclip (>=1.8.2,<2.0.0) ; extra == "clipboard"
@@ -0,0 +1,12 @@
1
+ hippius_sdk/__init__.py,sha256=-AyvKNLGenve9LKTus1qhOVFFkydlQceUaHr3fNimqQ,1391
2
+ hippius_sdk/cli.py,sha256=rG57m5RU35GiB7pNnZzlrSouyqxiALg2ZgAKcouKBHA,103880
3
+ hippius_sdk/client.py,sha256=mMKX_m2ZwfbGVAU3zasHZQF0ddToqypkxGKTylruB3Y,14901
4
+ hippius_sdk/config.py,sha256=WqocYwx-UomLeZ-iFUNDjg9vRcagOBA1Th68XELbuTs,22950
5
+ hippius_sdk/ipfs.py,sha256=oW3zX2rfdUWBQnh3oxdykrixbOxrB2_DJxRbXOo1v4w,54319
6
+ hippius_sdk/ipfs_core.py,sha256=w6ljgFdUzL-ffKxr4x_W-aYZTNpgyJg5HOFDrLKPILA,6690
7
+ hippius_sdk/substrate.py,sha256=9SkjIkMClkyE004H855C4t2VoJr6ypi87r6geBFEFeA,60091
8
+ hippius_sdk/utils.py,sha256=-N0w0RfXhwxJgSkSroxqFMw-0zJQXvcmxM0OS5UtWEY,4145
9
+ hippius-0.2.1.dist-info/METADATA,sha256=GzaP86Ps_TkJWrh55320bESjK1tdEpRvuFBAfFF_z4g,28032
10
+ hippius-0.2.1.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
11
+ hippius-0.2.1.dist-info/entry_points.txt,sha256=b1lo60zRXmv1ud-c5BC-cJcAfGE5FD4qM_nia6XeQtM,98
12
+ hippius-0.2.1.dist-info/RECORD,,
hippius_sdk/__init__.py CHANGED
@@ -26,7 +26,7 @@ from hippius_sdk.config import (
26
26
  from hippius_sdk.ipfs import IPFSClient
27
27
  from hippius_sdk.utils import format_cid, format_size, hex_to_ipfs_cid
28
28
 
29
- __version__ = "0.2.0"
29
+ __version__ = "0.2.1"
30
30
  __all__ = [
31
31
  "HippiusClient",
32
32
  "IPFSClient",
hippius_sdk/cli.py CHANGED
@@ -14,12 +14,8 @@ import getpass
14
14
  import inspect
15
15
  import json
16
16
  import os
17
- import random
18
17
  import sys
19
- import threading
20
18
  import time
21
- import uuid
22
- from typing import List, Optional
23
19
 
24
20
  from dotenv import load_dotenv
25
21
 
@@ -33,7 +29,6 @@ from hippius_sdk import (
33
29
  get_active_account,
34
30
  get_all_config,
35
31
  get_config_value,
36
- get_encryption_key,
37
32
  get_seed_phrase,
38
33
  initialize_from_env,
39
34
  list_accounts,
@@ -42,10 +37,8 @@ from hippius_sdk import (
42
37
  save_config,
43
38
  set_active_account,
44
39
  set_config_value,
45
- set_encryption_key,
46
40
  set_seed_phrase,
47
41
  )
48
- from hippius_sdk.substrate import FileInput
49
42
 
50
43
  try:
51
44
  import nacl.secret
@@ -55,10 +48,7 @@ except ImportError:
55
48
  else:
56
49
  ENCRYPTION_AVAILABLE = True
57
50
 
58
- # Load environment variables
59
51
  load_dotenv()
60
-
61
- # Initialize configuration from environment variables
62
52
  initialize_from_env()
63
53
 
64
54
 
@@ -414,7 +404,7 @@ async def handle_store_dir(client, dir_path, miner_ids, encrypt=None):
414
404
  return 0
415
405
 
416
406
 
417
- def handle_credits(client, account_address):
407
+ async def handle_credits(client, account_address):
418
408
  """Handle the credits command"""
419
409
  print("Checking free credits for the account...")
420
410
  try:
@@ -450,7 +440,7 @@ def handle_credits(client, account_address):
450
440
 
451
441
  return 1
452
442
 
453
- credits = client.substrate_client.get_free_credits(account_address)
443
+ credits = await client.substrate_client.get_free_credits(account_address)
454
444
  print(f"\nFree credits: {credits:.6f}")
455
445
  raw_value = int(
456
446
  credits * 1_000_000_000_000_000_000
@@ -793,7 +783,15 @@ async def handle_ec_files(
793
783
 
794
784
 
795
785
  async def handle_erasure_code(
796
- client, file_path, k, m, chunk_size, miner_ids, encrypt=None, verbose=True
786
+ client,
787
+ file_path,
788
+ k,
789
+ m,
790
+ chunk_size,
791
+ miner_ids,
792
+ encrypt=None,
793
+ publish=False,
794
+ verbose=True,
797
795
  ):
798
796
  """Handle the erasure-code command"""
799
797
  if not os.path.exists(file_path):
@@ -823,7 +821,6 @@ async def handle_erasure_code(
823
821
  for root, _, files in os.walk(file_path):
824
822
  if files:
825
823
  example_file = os.path.join(root, files[0])
826
- rel_path = os.path.relpath(example_file, os.path.dirname(file_path))
827
824
  print(f' hippius erasure-code "{example_file}" --k {k} --m {m}')
828
825
  break
829
826
 
@@ -835,7 +832,15 @@ async def handle_erasure_code(
835
832
 
836
833
  if choice in ("y", "yes"):
837
834
  return await handle_erasure_code_directory(
838
- client, file_path, k, m, chunk_size, miner_ids, encrypt, verbose
835
+ client,
836
+ file_path,
837
+ k,
838
+ m,
839
+ chunk_size,
840
+ miner_ids,
841
+ encrypt,
842
+ publish,
843
+ verbose,
839
844
  )
840
845
  else:
841
846
  print(f" No files found in directory {file_path}")
@@ -865,11 +870,6 @@ async def handle_erasure_code(
865
870
 
866
871
  print(f"Processing {file_path} ({file_size_mb:.2f} MB) with erasure coding...")
867
872
 
868
- # Check if the file is too small for the current chunk size and k value
869
- original_k = k
870
- original_m = m
871
- original_chunk_size = chunk_size
872
-
873
873
  # Calculate how many chunks we would get with current settings
874
874
  potential_chunks = max(1, file_size // chunk_size)
875
875
 
@@ -878,7 +878,7 @@ async def handle_erasure_code(
878
878
  # Calculate a new chunk size that would give us exactly k chunks
879
879
  new_chunk_size = max(1024, file_size // k) # Ensure at least 1KB chunks
880
880
 
881
- print(f"Warning: File is too small for the requested parameters.")
881
+ print("Warning: File is too small for the requested parameters.")
882
882
  print(
883
883
  f"Original parameters: k={k}, m={m}, chunk size={chunk_size/1024/1024:.2f} MB"
884
884
  )
@@ -910,27 +910,73 @@ async def handle_erasure_code(
910
910
  verbose=verbose,
911
911
  )
912
912
 
913
+ # Store the original result before potentially overwriting it with publish result
914
+ storage_result = result.copy()
915
+ metadata_cid = storage_result.get("metadata_cid", "unknown")
916
+
917
+ # If publish flag is set, publish to the global IPFS network
918
+ if publish:
919
+ if metadata_cid != "unknown":
920
+ print("\nPublishing to global IPFS network...")
921
+ try:
922
+ # Publish the metadata to the global IPFS network
923
+ publish_result = await client.ipfs_client.publish_global(
924
+ metadata_cid
925
+ )
926
+ if publish_result.get("published", False):
927
+ print("Successfully published to global IPFS network")
928
+ print(f"Access URL: https://ipfs.io/ipfs/{metadata_cid}")
929
+ else:
930
+ print(
931
+ f"Warning: {publish_result.get('message', 'Failed to publish to global network')}"
932
+ )
933
+ except Exception as e:
934
+ print(f"Warning: Failed to publish to global IPFS network: {e}")
935
+
913
936
  elapsed_time = time.time() - start_time
914
937
 
915
938
  print(f"\nErasure coding and storage completed in {elapsed_time:.2f} seconds!")
916
939
 
917
940
  # Display metadata
918
- metadata = result.get("metadata", {})
919
- metadata_cid = result.get("metadata_cid", "unknown")
920
- total_files_stored = result.get("total_files_stored", 0)
941
+ metadata = storage_result.get("metadata", {})
942
+ total_files_stored = storage_result.get("total_files_stored", 0)
921
943
 
922
944
  original_file = metadata.get("original_file", {})
923
945
  erasure_coding = metadata.get("erasure_coding", {})
924
946
 
925
- print("\nErasure Coding Summary:")
926
- print(
927
- f" Original file: {original_file.get('name')} ({original_file.get('size', 0)/1024/1024:.2f} MB)"
928
- )
929
- print(f" File ID: {erasure_coding.get('file_id')}")
930
- print(f" Parameters: k={erasure_coding.get('k')}, m={erasure_coding.get('m')}")
931
- print(f" Total chunks: {len(metadata.get('chunks', []))}")
932
- print(f" Total files stored in marketplace: {total_files_stored}")
933
- print(f" Metadata CID: {metadata_cid}")
947
+ # If metadata_cid is known but metadata is empty, try to get file info from result directly
948
+ if metadata_cid != "unknown" and not original_file:
949
+ file_name = os.path.basename(file_path)
950
+ file_size = os.path.getsize(file_path) if os.path.exists(file_path) else 0
951
+
952
+ # Use direct values from input parameters when metadata is not available
953
+ print("\nErasure Coding Summary:")
954
+ print(f" Original file: {file_name} ({file_size/1024/1024:.2f} MB)")
955
+ print(f" Parameters: k={k}, m={m}")
956
+ print(f" Total files stored in marketplace: {total_files_stored}")
957
+ print(f" Metadata CID: {metadata_cid}")
958
+
959
+ # Add publish status if applicable
960
+ if publish:
961
+ print(f" Published to global IPFS: Yes")
962
+ print(f" Global access URL: https://ipfs.io/ipfs/{metadata_cid}")
963
+ else:
964
+ print("\nErasure Coding Summary:")
965
+ print(
966
+ f" Original file: {original_file.get('name')} ({original_file.get('size', 0)/1024/1024:.2f} MB)"
967
+ )
968
+ print(f" File ID: {erasure_coding.get('file_id')}")
969
+ print(
970
+ f" Parameters: k={erasure_coding.get('k')}, m={erasure_coding.get('m')}"
971
+ )
972
+ print(f" Total chunks: {len(metadata.get('chunks', []))}")
973
+ print(f" Total files stored in marketplace: {total_files_stored}")
974
+ print(f" Metadata CID: {metadata_cid}")
975
+
976
+ # Add publish status if applicable
977
+ if publish:
978
+ print(f" Published to global IPFS: Yes")
979
+ print(f" Global access URL: https://ipfs.io/ipfs/{metadata_cid}")
934
980
 
935
981
  # If we stored in the marketplace
936
982
  if "transaction_hash" in result:
@@ -943,9 +989,13 @@ async def handle_erasure_code(
943
989
  print(f" 1. The metadata CID: {metadata_cid}")
944
990
  print(" 2. Access to at least k chunks for each original chunk")
945
991
  print("\nReconstruction command:")
946
- print(
947
- f" hippius reconstruct {metadata_cid} reconstructed_{original_file.get('name')}"
948
- )
992
+
993
+ # Get file name, either from metadata or directly from file path
994
+ output_filename = original_file.get("name")
995
+ if not output_filename:
996
+ output_filename = os.path.basename(file_path)
997
+
998
+ print(f" hippius reconstruct {metadata_cid} reconstructed_{output_filename}")
949
999
 
950
1000
  return 0
951
1001
 
@@ -966,7 +1016,15 @@ async def handle_erasure_code(
966
1016
 
967
1017
 
968
1018
  async def handle_erasure_code_directory(
969
- client, dir_path, k, m, chunk_size, miner_ids, encrypt=None, verbose=True
1019
+ client,
1020
+ dir_path,
1021
+ k,
1022
+ m,
1023
+ chunk_size,
1024
+ miner_ids,
1025
+ encrypt=None,
1026
+ publish=False,
1027
+ verbose=True,
970
1028
  ):
971
1029
  """Apply erasure coding to each file in a directory individually"""
972
1030
  if not os.path.isdir(dir_path):
@@ -1056,16 +1114,38 @@ async def handle_erasure_code_directory(
1056
1114
  verbose=False, # Less verbose for batch processing
1057
1115
  )
1058
1116
 
1059
- # Store basic result info
1117
+ metadata_cid = result.get("metadata_cid", "unknown")
1118
+ publishing_status = "Not published"
1119
+
1120
+ # If publish flag is set, publish to the global IPFS network
1121
+ if publish and metadata_cid != "unknown":
1122
+ try:
1123
+ # Publish the metadata to the global IPFS network
1124
+ publish_result = await client.ipfs_client.publish_global(
1125
+ metadata_cid
1126
+ )
1127
+ if publish_result.get("published", False):
1128
+ publishing_status = "Published to global IPFS"
1129
+ else:
1130
+ publishing_status = f"Failed to publish: {publish_result.get('message', 'Unknown error')}"
1131
+ except Exception as e:
1132
+ publishing_status = f"Failed to publish: {str(e)}"
1133
+
1134
+ # Store basic result info with additional publish info
1060
1135
  results.append(
1061
1136
  {
1062
1137
  "file_path": file_path,
1063
- "metadata_cid": result.get("metadata_cid", "unknown"),
1138
+ "metadata_cid": metadata_cid,
1064
1139
  "success": True,
1140
+ "published": publish
1141
+ and publishing_status == "Published to global IPFS",
1065
1142
  }
1066
1143
  )
1067
1144
 
1068
- print(f"Success! Metadata CID: {result.get('metadata_cid', 'unknown')}")
1145
+ status_msg = f"Success! Metadata CID: {metadata_cid}"
1146
+ if publish:
1147
+ status_msg += f" ({publishing_status})"
1148
+ print(status_msg)
1069
1149
  successful += 1
1070
1150
 
1071
1151
  except Exception as e:
@@ -1132,13 +1212,12 @@ async def handle_reconstruct(client, metadata_cid, output_file, verbose=True):
1132
1212
 
1133
1213
  try:
1134
1214
  # Use the reconstruct_from_erasure_code method
1135
- result = await client.reconstruct_from_erasure_code(
1215
+ await client.reconstruct_from_erasure_code(
1136
1216
  metadata_cid=metadata_cid, output_file=output_file, verbose=verbose
1137
1217
  )
1138
1218
 
1139
1219
  elapsed_time = time.time() - start_time
1140
1220
  print(f"\nFile reconstruction completed in {elapsed_time:.2f} seconds!")
1141
- print(f"Reconstructed file saved to: {result}")
1142
1221
 
1143
1222
  return 0
1144
1223
 
@@ -1405,6 +1484,200 @@ def handle_seed_phrase_status(account_name=None):
1405
1484
  return 0
1406
1485
 
1407
1486
 
1487
+ def handle_account_create(client, name, encrypt=False):
1488
+ """Handle creating a new account with a generated seed phrase"""
1489
+ print(f"Creating new account '{name}'...")
1490
+
1491
+ # Get password if encryption is requested
1492
+ password = None
1493
+ if encrypt:
1494
+ try:
1495
+ password = getpass.getpass("Enter password to encrypt seed phrase: ")
1496
+ password_confirm = getpass.getpass("Confirm password: ")
1497
+
1498
+ if password != password_confirm:
1499
+ print("Error: Passwords do not match")
1500
+ return 1
1501
+ except KeyboardInterrupt:
1502
+ print("\nOperation cancelled")
1503
+ return 1
1504
+
1505
+ try:
1506
+ # Create the account
1507
+ result = client.substrate_client.create_account(
1508
+ name, encode=encrypt, password=password
1509
+ )
1510
+
1511
+ print(f"Account created successfully!")
1512
+ print(f"Name: {result['name']}")
1513
+ print(f"Address: {result['address']}")
1514
+ print(f"Seed phrase: {result['mnemonic']}")
1515
+ print()
1516
+ print(
1517
+ "IMPORTANT: Please write down your seed phrase and store it in a safe place."
1518
+ )
1519
+ print(
1520
+ "It is the only way to recover your account if you lose access to this configuration."
1521
+ )
1522
+
1523
+ return 0
1524
+ except Exception as e:
1525
+ print(f"Error creating account: {e}")
1526
+ return 1
1527
+
1528
+
1529
+ def handle_account_export(client, name=None, file_path=None):
1530
+ """Handle exporting an account to a file"""
1531
+ try:
1532
+ # Export the account
1533
+ exported_file = client.substrate_client.export_account(
1534
+ account_name=name, file_path=file_path
1535
+ )
1536
+
1537
+ print(f"Account exported successfully to: {exported_file}")
1538
+ print("The exported file contains your seed phrase in plain text.")
1539
+ print("Please keep this file secure and do not share it with anyone.")
1540
+
1541
+ return 0
1542
+ except Exception as e:
1543
+ print(f"Error exporting account: {e}")
1544
+ return 1
1545
+
1546
+
1547
+ def handle_account_import(client, file_path, encrypt=False):
1548
+ """Handle importing an account from a file"""
1549
+ # Get password if encryption is requested
1550
+ password = None
1551
+ if encrypt:
1552
+ try:
1553
+ password = getpass.getpass("Enter password to encrypt seed phrase: ")
1554
+ password_confirm = getpass.getpass("Confirm password: ")
1555
+
1556
+ if password != password_confirm:
1557
+ print("Error: Passwords do not match")
1558
+ return 1
1559
+ except KeyboardInterrupt:
1560
+ print("\nOperation cancelled")
1561
+ return 1
1562
+
1563
+ try:
1564
+ # Import the account
1565
+ result = client.substrate_client.import_account(
1566
+ file_path, password=password if encrypt else None
1567
+ )
1568
+
1569
+ print(f"Account imported successfully!")
1570
+ print(f"Name: {result['name']}")
1571
+ print(f"Address: {result['address']}")
1572
+
1573
+ if (
1574
+ result.get("original_name")
1575
+ and result.get("original_name") != result["name"]
1576
+ ):
1577
+ print(
1578
+ f"Note: Original name '{result['original_name']}' was already in use, renamed to '{result['name']}'"
1579
+ )
1580
+
1581
+ return 0
1582
+ except Exception as e:
1583
+ print(f"Error importing account: {e}")
1584
+ return 1
1585
+
1586
+
1587
+ async def handle_account_info(client, account_name=None, include_history=False):
1588
+ """Handle showing account information"""
1589
+ try:
1590
+ # Get account info - properly await the async method
1591
+ info = await client.substrate_client.get_account_info(
1592
+ account_name, include_history=include_history
1593
+ )
1594
+
1595
+ active_marker = " (active)" if info.get("is_active", False) else ""
1596
+ encoded_status = (
1597
+ "encrypted" if info.get("seed_phrase_encrypted", False) else "plain text"
1598
+ )
1599
+
1600
+ print(f"Account: {info['name']}{active_marker}")
1601
+ print(f"Address: {info['address']}")
1602
+ print(f"Seed phrase: {encoded_status}")
1603
+
1604
+ # Show storage statistics if available
1605
+ if "storage_stats" in info:
1606
+ stats = info["storage_stats"]
1607
+ if "error" in stats:
1608
+ print(f"Storage stats: Error - {stats['error']}")
1609
+ else:
1610
+ print(f"Files stored: {stats['files']}")
1611
+ print(f"Total storage: {stats['size_formatted']}")
1612
+
1613
+ # Show balance if available
1614
+ if "balance" in info:
1615
+ balance = info["balance"]
1616
+ print("\nAccount Balance:")
1617
+ print(f" Free: {balance['free']:.6f}")
1618
+ print(f" Reserved: {balance['reserved']:.6f}")
1619
+ print(f" Total: {balance['total']:.6f}")
1620
+
1621
+ # Show free credits if available
1622
+ if "free_credits" in info:
1623
+ print(f"Free credits: {info['free_credits']:.6f}")
1624
+
1625
+ # Show file list if requested and available
1626
+ if include_history and "files" in info and info["files"]:
1627
+ print(f"\nStored Files ({len(info['files'])}):")
1628
+ for i, file in enumerate(info["files"], 1):
1629
+ print(f" {i}. {file.get('file_name', 'Unnamed')}")
1630
+ print(f" CID: {file.get('file_hash', 'Unknown')}")
1631
+ print(f" Size: {file.get('size_formatted', 'Unknown')}")
1632
+
1633
+ return 0
1634
+ except Exception as e:
1635
+ print(f"Error retrieving account info: {e}")
1636
+ return 1
1637
+
1638
+
1639
+ async def handle_account_balance(client, account_name=None, watch=False, interval=5):
1640
+ """Handle checking or watching account balance"""
1641
+ try:
1642
+ # Get the account address
1643
+ if account_name:
1644
+ address = get_account_address(account_name)
1645
+ if not address:
1646
+ print(f"Error: Could not find address for account '{account_name}'")
1647
+ return 1
1648
+ else:
1649
+ if client.substrate_client._account_address:
1650
+ address = client.substrate_client._account_address
1651
+ else:
1652
+ print("Error: No account address available")
1653
+ return 1
1654
+
1655
+ if watch:
1656
+ # Watch mode - continuous updates
1657
+ # Note: watch_account_balance may need to be modified to be async-compatible
1658
+ await client.substrate_client.watch_account_balance(address, interval)
1659
+ else:
1660
+ # One-time check
1661
+ balance = await client.substrate_client.get_account_balance(address)
1662
+
1663
+ print(f"Account Balance for: {address}")
1664
+ print(f"Free: {balance['free']:.6f}")
1665
+ print(f"Reserved: {balance['reserved']:.6f}")
1666
+ print(f"Frozen: {balance['frozen']:.6f}")
1667
+ print(f"Total: {balance['total']:.6f}")
1668
+
1669
+ # Show raw values
1670
+ print("\nRaw Values:")
1671
+ print(f"Free: {balance['raw']['free']:,}")
1672
+ print(f"Reserved: {balance['raw']['reserved']:,}")
1673
+ print(f"Frozen: {balance['raw']['frozen']:,}")
1674
+
1675
+ return 0
1676
+ except Exception as e:
1677
+ print(f"Error checking account balance: {e}")
1678
+ return 1
1679
+
1680
+
1408
1681
  def handle_account_list():
1409
1682
  """Handle listing all accounts"""
1410
1683
  accounts = list_accounts()
@@ -1813,6 +2086,9 @@ examples:
1813
2086
  # Erasure code a file (Reed-Solomon)
1814
2087
  hippius erasure-code large_file.mp4 --k 3 --m 5
1815
2088
 
2089
+ # Erasure code and publish to global IPFS network
2090
+ hippius erasure-code large_file.avi --publish
2091
+
1816
2092
  # Reconstruct an erasure-coded file
1817
2093
  hippius reconstruct QmMetadataHash reconstructed_file.mp4
1818
2094
  """,
@@ -2049,6 +2325,11 @@ examples:
2049
2325
  erasure_code_parser.add_argument(
2050
2326
  "--no-encrypt", action="store_true", help="Do not encrypt the file"
2051
2327
  )
2328
+ erasure_code_parser.add_argument(
2329
+ "--publish",
2330
+ action="store_true",
2331
+ help="Upload and publish the erasure-coded file to the global IPFS network",
2332
+ )
2052
2333
  erasure_code_parser.add_argument(
2053
2334
  "--verbose", action="store_true", help="Enable verbose output", default=True
2054
2335
  )
@@ -2156,6 +2437,77 @@ examples:
2156
2437
  # List accounts
2157
2438
  account_subparsers.add_parser("list", help="List all accounts")
2158
2439
 
2440
+ # Create account
2441
+ create_account_parser = account_subparsers.add_parser(
2442
+ "create", help="Create a new account with a generated seed phrase"
2443
+ )
2444
+ create_account_parser.add_argument(
2445
+ "--name", required=True, help="Name for the new account"
2446
+ )
2447
+ create_account_parser.add_argument(
2448
+ "--encrypt", action="store_true", help="Encrypt the seed phrase with a password"
2449
+ )
2450
+
2451
+ # Export account
2452
+ export_account_parser = account_subparsers.add_parser(
2453
+ "export", help="Export an account to a file"
2454
+ )
2455
+ export_account_parser.add_argument(
2456
+ "--name",
2457
+ help="Name of the account to export (uses active account if not specified)",
2458
+ )
2459
+ export_account_parser.add_argument(
2460
+ "--file",
2461
+ help="Path to save the exported account file (auto-generated if not specified)",
2462
+ )
2463
+
2464
+ # Import account
2465
+ import_account_parser = account_subparsers.add_parser(
2466
+ "import", help="Import an account from a file"
2467
+ )
2468
+ import_account_parser.add_argument(
2469
+ "--file", required=True, help="Path to the account file to import"
2470
+ )
2471
+ import_account_parser.add_argument(
2472
+ "--encrypt",
2473
+ action="store_true",
2474
+ help="Encrypt the imported seed phrase with a password",
2475
+ )
2476
+
2477
+ # Account info
2478
+ info_account_parser = account_subparsers.add_parser(
2479
+ "info", help="Display detailed information about an account"
2480
+ )
2481
+ info_account_parser.add_argument(
2482
+ "account_name",
2483
+ nargs="?",
2484
+ help="Name of the account to show (uses active account if not specified)",
2485
+ )
2486
+ info_account_parser.add_argument(
2487
+ "--history", action="store_true", help="Include usage history in the output"
2488
+ )
2489
+
2490
+ # Account balance
2491
+ balance_account_parser = account_subparsers.add_parser(
2492
+ "balance", help="Check account balance"
2493
+ )
2494
+ balance_account_parser.add_argument(
2495
+ "account_name",
2496
+ nargs="?",
2497
+ help="Name of the account to check (uses active account if not specified)",
2498
+ )
2499
+ balance_account_parser.add_argument(
2500
+ "--watch",
2501
+ action="store_true",
2502
+ help="Watch account balance in real-time until Ctrl+C is pressed",
2503
+ )
2504
+ balance_account_parser.add_argument(
2505
+ "--interval",
2506
+ type=int,
2507
+ default=5,
2508
+ help="Update interval in seconds for watch mode (default: 5)",
2509
+ )
2510
+
2159
2511
  # Switch active account
2160
2512
  switch_account_parser = account_subparsers.add_parser(
2161
2513
  "switch", help="Switch to a different account"
@@ -2290,7 +2642,7 @@ examples:
2290
2642
  )
2291
2643
 
2292
2644
  elif args.command == "credits":
2293
- return handle_credits(client, args.account_address)
2645
+ return run_async_handler(handle_credits, client, args.account_address)
2294
2646
 
2295
2647
  elif args.command == "files":
2296
2648
  return run_async_handler(
@@ -2335,6 +2687,7 @@ examples:
2335
2687
  args.chunk_size,
2336
2688
  miner_ids,
2337
2689
  encrypt=args.encrypt,
2690
+ publish=args.publish,
2338
2691
  verbose=args.verbose,
2339
2692
  )
2340
2693
 
@@ -2398,6 +2751,24 @@ examples:
2398
2751
  elif args.command == "account":
2399
2752
  if args.account_action == "list":
2400
2753
  return handle_account_list()
2754
+ elif args.account_action == "create":
2755
+ return handle_account_create(client, args.name, args.encrypt)
2756
+ elif args.account_action == "export":
2757
+ return handle_account_export(client, args.name, args.file)
2758
+ elif args.account_action == "import":
2759
+ return handle_account_import(client, args.file, args.encrypt)
2760
+ elif args.account_action == "info":
2761
+ return run_async_handler(
2762
+ handle_account_info, client, args.account_name, args.history
2763
+ )
2764
+ elif args.account_action == "balance":
2765
+ return run_async_handler(
2766
+ handle_account_balance,
2767
+ client,
2768
+ args.account_name,
2769
+ args.watch,
2770
+ args.interval,
2771
+ )
2401
2772
  elif args.account_action == "switch":
2402
2773
  return handle_account_switch(args.account_name)
2403
2774
  elif args.account_action == "delete":