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.
- {hippius-0.2.0.dist-info → hippius-0.2.1.dist-info}/METADATA +2 -1
- hippius-0.2.1.dist-info/RECORD +12 -0
- hippius_sdk/__init__.py +1 -1
- hippius_sdk/cli.py +414 -43
- hippius_sdk/ipfs.py +124 -20
- hippius_sdk/substrate.py +468 -10
- hippius-0.2.0.dist-info/RECORD +0 -12
- {hippius-0.2.0.dist-info → hippius-0.2.1.dist-info}/WHEEL +0 -0
- {hippius-0.2.0.dist-info → hippius-0.2.1.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: hippius
|
3
|
-
Version: 0.2.
|
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
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,
|
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,
|
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(
|
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 =
|
919
|
-
|
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
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
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
|
-
|
947
|
-
|
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,
|
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
|
-
|
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":
|
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
|
-
|
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
|
-
|
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
|
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":
|