hippius 0.2.3__py3-none-any.whl → 0.2.4__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.3.dist-info → hippius-0.2.4.dist-info}/METADATA +207 -151
- hippius-0.2.4.dist-info/RECORD +16 -0
- hippius_sdk/__init__.py +10 -21
- hippius_sdk/cli.py +7 -1
- hippius_sdk/cli_assets.py +8 -6
- hippius_sdk/cli_handlers.py +540 -137
- hippius_sdk/cli_parser.py +5 -0
- hippius_sdk/cli_rich.py +2 -8
- hippius_sdk/client.py +14 -14
- hippius_sdk/config.py +108 -141
- hippius_sdk/ipfs.py +64 -43
- hippius_sdk/substrate.py +24 -33
- hippius-0.2.3.dist-info/RECORD +0 -16
- {hippius-0.2.3.dist-info → hippius-0.2.4.dist-info}/WHEEL +0 -0
- {hippius-0.2.3.dist-info → hippius-0.2.4.dist-info}/entry_points.txt +0 -0
hippius_sdk/cli_handlers.py
CHANGED
@@ -10,11 +10,9 @@ import base64
|
|
10
10
|
import getpass
|
11
11
|
import json
|
12
12
|
import os
|
13
|
-
import pprint
|
14
|
-
import sys
|
15
13
|
import tempfile
|
16
14
|
import time
|
17
|
-
from typing import Any,
|
15
|
+
from typing import Any, List, Optional
|
18
16
|
|
19
17
|
from hippius_sdk import (
|
20
18
|
HippiusClient,
|
@@ -26,8 +24,6 @@ from hippius_sdk import (
|
|
26
24
|
get_active_account,
|
27
25
|
get_all_config,
|
28
26
|
get_config_value,
|
29
|
-
get_seed_phrase,
|
30
|
-
initialize_from_env,
|
31
27
|
list_accounts,
|
32
28
|
load_config,
|
33
29
|
reset_config,
|
@@ -98,6 +94,21 @@ def create_client(args: Any) -> HippiusClient:
|
|
98
94
|
# Get substrate URL
|
99
95
|
substrate_url = args.substrate_url if hasattr(args, "substrate_url") else None
|
100
96
|
|
97
|
+
# Skip password if we're doing erasure-code with --no-publish
|
98
|
+
# This avoids prompting for password when we don't need to interact with the blockchain
|
99
|
+
password = None
|
100
|
+
if (
|
101
|
+
hasattr(args, "command")
|
102
|
+
and args.command == "erasure-code"
|
103
|
+
and hasattr(args, "no_publish")
|
104
|
+
and args.no_publish
|
105
|
+
):
|
106
|
+
# Don't need a password in this case
|
107
|
+
password = None
|
108
|
+
else:
|
109
|
+
# Use password from args if provided
|
110
|
+
password = args.password if hasattr(args, "password") else None
|
111
|
+
|
101
112
|
# Initialize client with provided parameters
|
102
113
|
client = HippiusClient(
|
103
114
|
ipfs_gateway=gateway,
|
@@ -106,7 +117,7 @@ def create_client(args: Any) -> HippiusClient:
|
|
106
117
|
substrate_seed_phrase=(
|
107
118
|
args.seed_phrase if hasattr(args, "seed_phrase") else None
|
108
119
|
),
|
109
|
-
seed_phrase_password=
|
120
|
+
seed_phrase_password=password,
|
110
121
|
account_name=args.account if hasattr(args, "account") else None,
|
111
122
|
encrypt_by_default=encrypt,
|
112
123
|
encryption_key=encryption_key,
|
@@ -160,7 +171,7 @@ async def handle_exists(client: HippiusClient, cid: str) -> int:
|
|
160
171
|
log(f"Gateway URL: [link]{result['gateway_url']}[/link]")
|
161
172
|
|
162
173
|
# Display download command in a panel
|
163
|
-
command = f"hippius download {formatted_cid} <output_path>"
|
174
|
+
command = f"[bold green underline]hippius download {formatted_cid} <output_path>[/bold green underline]"
|
164
175
|
print_panel(command, title="Download Command")
|
165
176
|
else:
|
166
177
|
error(f"CID [bold cyan]{formatted_cid}[/bold cyan] does not exist on IPFS")
|
@@ -331,7 +342,7 @@ async def handle_store(
|
|
331
342
|
)
|
332
343
|
|
333
344
|
# Display download command in a panel
|
334
|
-
command = f"hippius download {result['cid']} <output_path>"
|
345
|
+
command = f"[bold green underline]hippius download {result['cid']} <output_path>[/bold green underline]"
|
335
346
|
print_panel(command, title="Download Command")
|
336
347
|
|
337
348
|
return 0
|
@@ -498,8 +509,7 @@ async def handle_credits(
|
|
498
509
|
"Please provide an account address with '--account_address' or set a default with:"
|
499
510
|
)
|
500
511
|
log(
|
501
|
-
" hippius address set-default <your_account_address>"
|
502
|
-
style="bold blue",
|
512
|
+
" [bold green underline]hippius address set-default <your_account_address>[/bold green underline]"
|
503
513
|
)
|
504
514
|
|
505
515
|
return 1
|
@@ -555,7 +565,9 @@ async def handle_files(
|
|
555
565
|
info(
|
556
566
|
"Please provide an account address with '--account_address' or set a default with:"
|
557
567
|
)
|
558
|
-
log(
|
568
|
+
log(
|
569
|
+
" [bold green underline]hippius address set-default <your_account_address>[/bold green underline]"
|
570
|
+
)
|
559
571
|
return 1
|
560
572
|
|
561
573
|
# Get files from the marketplace
|
@@ -707,7 +719,9 @@ async def handle_ec_files(
|
|
707
719
|
info(
|
708
720
|
"Please provide an account address with '--account_address' or set a default with:"
|
709
721
|
)
|
710
|
-
log(
|
722
|
+
log(
|
723
|
+
" [bold green underline]hippius address set-default <your_account_address>[/bold green underline]"
|
724
|
+
)
|
711
725
|
return 1
|
712
726
|
|
713
727
|
info(f"Getting erasure-coded files for account: [bold]{account_address}[/bold]")
|
@@ -937,7 +951,7 @@ async def handle_ec_files(
|
|
937
951
|
# Include a real example with the first metadata CID
|
938
952
|
example_cid = metadata_cids[0] if metadata_cids else "<METADATA_CID>"
|
939
953
|
print_panel(
|
940
|
-
f"hippius reconstruct <METADATA_CID> <OUTPUT_FILENAME
|
954
|
+
f"[bold green underline]hippius reconstruct <METADATA_CID> <OUTPUT_FILENAME>[/bold green underline]\n\nExample:\n[bold green underline]hippius reconstruct {example_cid} reconstructed_file.bin[/bold green underline]",
|
941
955
|
title="Reconstruction Command",
|
942
956
|
)
|
943
957
|
|
@@ -969,8 +983,12 @@ async def handle_erasure_code(
|
|
969
983
|
import zfec
|
970
984
|
except ImportError:
|
971
985
|
error("zfec is required for erasure coding")
|
972
|
-
log(
|
973
|
-
|
986
|
+
log(
|
987
|
+
"Install it with: [bold green underline]pip install zfec[/bold green underline]"
|
988
|
+
)
|
989
|
+
log(
|
990
|
+
"Then update your environment: [bold green underline]poetry add zfec[/bold green underline]"
|
991
|
+
)
|
974
992
|
return 1
|
975
993
|
|
976
994
|
# Get file size
|
@@ -989,11 +1007,14 @@ async def handle_erasure_code(
|
|
989
1007
|
# Calculate new chunk size to get exactly k chunks
|
990
1008
|
new_chunk_size = file_size / k
|
991
1009
|
|
1010
|
+
new_chunk_size = int(new_chunk_size)
|
1011
|
+
new_chunk_size = max(1, new_chunk_size)
|
1012
|
+
|
992
1013
|
# Create a panel with parameter adjustment information
|
993
1014
|
adjustment_info = [
|
994
|
-
f"Original parameters: k=[bold]{k}[/bold], m=[bold]{m}[/bold], chunk size=[bold]{chunk_size/1024/1024:.2f} MB[/bold]",
|
1015
|
+
f"Original parameters: k=[bold]{k}[/bold], m=[bold]{m}[/bold], chunk size=[bold]{chunk_size / 1024 / 1024:.2f} MB[/bold]",
|
995
1016
|
f"Would create only [bold red]{potential_chunks:.2f}[/bold red] chunks, which is less than k=[bold]{k}[/bold]",
|
996
|
-
f"Automatically adjusting chunk size to [bold green]{new_chunk_size/1024/1024:.6f} MB[/bold green] to create at least {k} chunks",
|
1017
|
+
f"Automatically adjusting chunk size to [bold green]{new_chunk_size / 1024 / 1024:.6f} MB[/bold green] to create at least {k} chunks",
|
997
1018
|
]
|
998
1019
|
print_panel("\n".join(adjustment_info), title="Parameter Adjustment")
|
999
1020
|
|
@@ -1001,9 +1022,9 @@ async def handle_erasure_code(
|
|
1001
1022
|
|
1002
1023
|
# Create parameter information panel
|
1003
1024
|
param_info = [
|
1004
|
-
f"File: [bold]{file_name}[/bold] ([bold cyan]{file_size/1024/1024:.2f} MB[/bold cyan])",
|
1025
|
+
f"File: [bold]{file_name}[/bold] ([bold cyan]{file_size / 1024 / 1024:.2f} MB[/bold cyan])",
|
1005
1026
|
f"Parameters: k=[bold]{k}[/bold], m=[bold]{m}[/bold] (need {k} of {m} chunks to reconstruct)",
|
1006
|
-
f"Chunk size: [bold cyan]{chunk_size/1024/1024:.6f} MB[/bold cyan]",
|
1027
|
+
f"Chunk size: [bold cyan]{chunk_size / 1024 / 1024:.6f} MB[/bold cyan]",
|
1007
1028
|
]
|
1008
1029
|
|
1009
1030
|
# Add encryption status
|
@@ -1012,6 +1033,16 @@ async def handle_erasure_code(
|
|
1012
1033
|
else:
|
1013
1034
|
param_info.append("[bold yellow]Encryption: Disabled[/bold yellow]")
|
1014
1035
|
|
1036
|
+
# Add publish status
|
1037
|
+
if publish:
|
1038
|
+
param_info.append(
|
1039
|
+
"[bold blue]Publishing: Enabled[/bold blue] (will store on blockchain)"
|
1040
|
+
)
|
1041
|
+
else:
|
1042
|
+
param_info.append(
|
1043
|
+
"[bold cyan]Publishing: Disabled[/bold cyan] (local only, no password needed)"
|
1044
|
+
)
|
1045
|
+
|
1015
1046
|
# Parse miner IDs if provided
|
1016
1047
|
miner_id_list = None
|
1017
1048
|
if miner_ids:
|
@@ -1028,7 +1059,9 @@ async def handle_erasure_code(
|
|
1028
1059
|
# Create progress for the erasure coding operation
|
1029
1060
|
with create_progress() as progress:
|
1030
1061
|
# Add tasks for the different stages
|
1031
|
-
processing_task = progress.add_task(
|
1062
|
+
processing_task = progress.add_task(
|
1063
|
+
"[cyan]Processing file...", total=100, visible=False
|
1064
|
+
)
|
1032
1065
|
encoding_task = progress.add_task(
|
1033
1066
|
"[green]Encoding chunks...", total=100, visible=False
|
1034
1067
|
)
|
@@ -1087,6 +1120,7 @@ async def handle_erasure_code(
|
|
1087
1120
|
max_retries=3,
|
1088
1121
|
verbose=verbose,
|
1089
1122
|
progress_callback=update_progress_bar,
|
1123
|
+
publish=publish,
|
1090
1124
|
)
|
1091
1125
|
|
1092
1126
|
# Complete all progress tasks
|
@@ -1145,7 +1179,7 @@ async def handle_erasure_code(
|
|
1145
1179
|
# Use direct values from input parameters when metadata is not available
|
1146
1180
|
summary_lines.extend(
|
1147
1181
|
[
|
1148
|
-
f"Original file: [bold]{file_name}[/bold] ([bold cyan]{file_size/1024/1024:.2f} MB[/bold cyan])",
|
1182
|
+
f"Original file: [bold]{file_name}[/bold] ([bold cyan]{file_size / 1024 / 1024:.2f} MB[/bold cyan])",
|
1149
1183
|
f"Parameters: k=[bold]{k}[/bold], m=[bold]{m}[/bold]",
|
1150
1184
|
f"Total files stored in marketplace: [bold]{total_files_stored}[/bold]",
|
1151
1185
|
f"Metadata CID: [bold cyan]{metadata_cid}[/bold cyan]",
|
@@ -1156,14 +1190,14 @@ async def handle_erasure_code(
|
|
1156
1190
|
if publish:
|
1157
1191
|
summary_lines.extend(
|
1158
1192
|
[
|
1159
|
-
|
1193
|
+
"Published to global IPFS: [bold green]Yes[/bold green]",
|
1160
1194
|
f"Global access URL: [link]{client.ipfs_client.gateway}/ipfs/{metadata_cid}[/link]",
|
1161
1195
|
]
|
1162
1196
|
)
|
1163
1197
|
else:
|
1164
1198
|
summary_lines.extend(
|
1165
1199
|
[
|
1166
|
-
f"Original file: [bold]{original_file.get('name')}[/bold] ([bold cyan]{original_file.get('size', 0)/1024/1024:.2f} MB[/bold cyan])",
|
1200
|
+
f"Original file: [bold]{original_file.get('name')}[/bold] ([bold cyan]{original_file.get('size', 0) / 1024 / 1024:.2f} MB[/bold cyan])",
|
1167
1201
|
f"File ID: [bold]{erasure_coding.get('file_id')}[/bold]",
|
1168
1202
|
f"Parameters: k=[bold]{erasure_coding.get('k')}[/bold], m=[bold]{erasure_coding.get('m')}[/bold]",
|
1169
1203
|
f"Total chunks: [bold]{len(metadata.get('chunks', []))}[/bold]",
|
@@ -1176,7 +1210,7 @@ async def handle_erasure_code(
|
|
1176
1210
|
if publish:
|
1177
1211
|
summary_lines.extend(
|
1178
1212
|
[
|
1179
|
-
|
1213
|
+
"Published to global IPFS: [bold green]Yes[/bold green]",
|
1180
1214
|
f"Global access URL: [link]{client.ipfs_client.gateway}/ipfs/{metadata_cid}[/link]",
|
1181
1215
|
]
|
1182
1216
|
)
|
@@ -1202,7 +1236,7 @@ async def handle_erasure_code(
|
|
1202
1236
|
f" 2. Access to at least [bold]{k}[/bold] chunks for each original chunk",
|
1203
1237
|
"",
|
1204
1238
|
"Reconstruction command:",
|
1205
|
-
f"[bold]hippius reconstruct {metadata_cid} reconstructed_{output_filename}[/bold]",
|
1239
|
+
f"[bold green underline]hippius reconstruct {metadata_cid} reconstructed_{output_filename}[/bold green underline]",
|
1206
1240
|
]
|
1207
1241
|
|
1208
1242
|
print_panel(
|
@@ -1593,67 +1627,116 @@ def handle_seed_phrase_set(
|
|
1593
1627
|
def handle_seed_phrase_encode(account_name: Optional[str] = None) -> int:
|
1594
1628
|
"""Handle the seed encode command"""
|
1595
1629
|
try:
|
1596
|
-
# Check if
|
1597
|
-
|
1630
|
+
# Check if account exists and get its encryption status
|
1631
|
+
config = load_config()
|
1632
|
+
accounts = config.get("substrate", {}).get("accounts", {})
|
1598
1633
|
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1605
|
-
|
1606
|
-
|
1634
|
+
# If account name not specified, use active account
|
1635
|
+
if not account_name:
|
1636
|
+
account_name = config.get("substrate", {}).get("active_account")
|
1637
|
+
if not account_name:
|
1638
|
+
error("No account specified and no active account")
|
1639
|
+
return 1
|
1640
|
+
|
1641
|
+
# Check if the account exists
|
1642
|
+
if account_name not in accounts:
|
1643
|
+
error(f"Account '{account_name}' not found")
|
1607
1644
|
return 1
|
1608
1645
|
|
1609
|
-
#
|
1610
|
-
|
1611
|
-
|
1646
|
+
# Get account details
|
1647
|
+
account = accounts.get(account_name, {})
|
1648
|
+
is_encrypted = account.get("seed_phrase_encoded", False)
|
1649
|
+
seed_phrase = account.get("seed_phrase")
|
1612
1650
|
|
1613
|
-
if
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1651
|
+
# Check if we have a seed phrase
|
1652
|
+
if not seed_phrase:
|
1653
|
+
error(f"Account '{account_name}' doesn't have a seed phrase")
|
1654
|
+
info(
|
1655
|
+
f"Set a seed phrase first with: [bold green underline]hippius seed set <seed_phrase> --account {account_name}[/bold green underline]"
|
1656
|
+
)
|
1657
|
+
return 1
|
1618
1658
|
|
1659
|
+
# Check if the seed phrase is already encrypted
|
1619
1660
|
if is_encrypted:
|
1620
|
-
|
1661
|
+
info("Seed phrase is already encrypted")
|
1621
1662
|
confirm = (
|
1622
1663
|
input("Do you want to re-encrypt it with a new password? (y/n): ")
|
1623
1664
|
.strip()
|
1624
1665
|
.lower()
|
1625
1666
|
)
|
1626
1667
|
if confirm != "y":
|
1627
|
-
|
1668
|
+
info("Encryption cancelled")
|
1628
1669
|
return 0
|
1629
1670
|
|
1630
|
-
|
1631
|
-
|
1671
|
+
# Need to decrypt with old password first
|
1672
|
+
old_password = getpass.getpass("Enter your current password to decrypt: ")
|
1673
|
+
decrypted_seed_phrase = decrypt_seed_phrase(old_password, account_name)
|
1674
|
+
|
1675
|
+
if not decrypted_seed_phrase:
|
1676
|
+
error("Unable to decrypt the seed phrase. Incorrect password?")
|
1677
|
+
return 1
|
1678
|
+
|
1679
|
+
# Now we have the decrypted seed phrase
|
1680
|
+
seed_phrase = decrypted_seed_phrase
|
1681
|
+
|
1682
|
+
# Get new password for encryption
|
1683
|
+
info("\nYou are about to encrypt your seed phrase.")
|
1632
1684
|
password = getpass.getpass("Enter a password for encryption: ")
|
1633
1685
|
confirm = getpass.getpass("Confirm password: ")
|
1634
1686
|
|
1635
1687
|
if password != confirm:
|
1636
|
-
|
1688
|
+
error("Passwords do not match")
|
1637
1689
|
return 1
|
1638
1690
|
|
1639
1691
|
if not password:
|
1640
|
-
|
1692
|
+
error("Password cannot be empty for encryption")
|
1641
1693
|
return 1
|
1642
1694
|
|
1643
|
-
#
|
1644
|
-
encrypt_seed_phrase(password, account_name)
|
1695
|
+
# Now encrypt the seed phrase - key fix here passing correct parameters
|
1696
|
+
success = encrypt_seed_phrase(seed_phrase, password, account_name)
|
1645
1697
|
|
1646
|
-
|
1647
|
-
|
1698
|
+
# Security: Clear the plaintext seed phrase from memory
|
1699
|
+
# This is a best-effort approach, as Python's garbage collection may still keep copies
|
1700
|
+
seed_phrase = None
|
1701
|
+
|
1702
|
+
if success:
|
1703
|
+
# Create success panel with encryption information
|
1704
|
+
encryption_info = [
|
1705
|
+
f"Account: [bold]{account_name}[/bold]",
|
1706
|
+
"[bold green]Seed phrase encrypted successfully[/bold green]",
|
1707
|
+
"",
|
1708
|
+
"You will need to provide this password when using the account for:",
|
1709
|
+
" - Pinning files to IPFS",
|
1710
|
+
" - Erasure coding with publishing",
|
1711
|
+
" - Any other blockchain operations",
|
1712
|
+
"",
|
1713
|
+
"[bold yellow underline]Security note:[/bold yellow underline] The original unencrypted seed phrase is NOT stored in the config.",
|
1714
|
+
]
|
1715
|
+
|
1716
|
+
# Try to get the address for display
|
1717
|
+
try:
|
1718
|
+
address = get_account_address(account_name)
|
1719
|
+
if address:
|
1720
|
+
encryption_info.append("")
|
1721
|
+
encryption_info.append(
|
1722
|
+
f"Account address: [bold cyan]{address}[/bold cyan]"
|
1723
|
+
)
|
1724
|
+
except Exception:
|
1725
|
+
pass
|
1726
|
+
|
1727
|
+
print_panel("\n".join(encryption_info), title="Encryption Successful")
|
1728
|
+
return 0
|
1729
|
+
else:
|
1730
|
+
error("Failed to encrypt seed phrase")
|
1731
|
+
return 1
|
1648
1732
|
|
1649
|
-
return 0
|
1650
1733
|
except Exception as e:
|
1651
|
-
|
1734
|
+
error(f"Error encrypting seed phrase: {e}")
|
1652
1735
|
return 1
|
1653
1736
|
|
1654
1737
|
|
1655
1738
|
def handle_seed_phrase_decode(account_name: Optional[str] = None) -> int:
|
1656
|
-
"""Handle the seed decode command"""
|
1739
|
+
"""Handle the seed decode command - temporarily decrypts and displays the seed phrase"""
|
1657
1740
|
try:
|
1658
1741
|
# Check if seed phrase exists and is encrypted
|
1659
1742
|
config = load_config()
|
@@ -1661,19 +1744,29 @@ def handle_seed_phrase_decode(account_name: Optional[str] = None) -> int:
|
|
1661
1744
|
|
1662
1745
|
if account_name:
|
1663
1746
|
account = accounts.get(account_name, {})
|
1664
|
-
is_encrypted = account.get("
|
1747
|
+
is_encrypted = account.get("seed_phrase_encoded", False)
|
1665
1748
|
else:
|
1666
|
-
|
1749
|
+
# Get active account
|
1750
|
+
active_account = config.get("substrate", {}).get("active_account")
|
1751
|
+
if active_account and active_account in accounts:
|
1752
|
+
is_encrypted = accounts[active_account].get(
|
1753
|
+
"seed_phrase_encoded", False
|
1754
|
+
)
|
1755
|
+
else:
|
1756
|
+
# Legacy mode
|
1757
|
+
is_encrypted = config.get("substrate", {}).get(
|
1758
|
+
"seed_phrase_encoded", False
|
1759
|
+
)
|
1667
1760
|
|
1668
1761
|
if not is_encrypted:
|
1669
|
-
|
1762
|
+
info("Seed phrase is not encrypted")
|
1670
1763
|
return 0
|
1671
1764
|
|
1672
1765
|
# Get password for decryption
|
1673
1766
|
password = getpass.getpass("Enter your password to decrypt the seed phrase: ")
|
1674
1767
|
|
1675
1768
|
if not password:
|
1676
|
-
|
1769
|
+
error("Password cannot be empty")
|
1677
1770
|
return 1
|
1678
1771
|
|
1679
1772
|
# Try to decrypt the seed phrase
|
@@ -1681,30 +1774,40 @@ def handle_seed_phrase_decode(account_name: Optional[str] = None) -> int:
|
|
1681
1774
|
seed_phrase = decrypt_seed_phrase(password, account_name)
|
1682
1775
|
|
1683
1776
|
if seed_phrase:
|
1684
|
-
|
1685
|
-
|
1777
|
+
# Create info panel for the decrypted seed phrase
|
1778
|
+
seed_info = [
|
1779
|
+
f"Decrypted seed phrase: [bold yellow]{seed_phrase}[/bold yellow]",
|
1780
|
+
"",
|
1781
|
+
"[bold green underline]NOTE: This is a temporary decryption only. Your seed phrase remains encrypted in the config.[/bold green underline]",
|
1782
|
+
"",
|
1783
|
+
"[bold red underline]SECURITY WARNING:[/bold red underline]",
|
1784
|
+
"- Your seed phrase gives full access to your account funds",
|
1785
|
+
"- Never share it with anyone or store it in an insecure location",
|
1786
|
+
"- Be aware that displaying it on screen could expose it to screen capture",
|
1787
|
+
"- Consider clearing your terminal history after this operation",
|
1788
|
+
]
|
1686
1789
|
|
1687
|
-
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1790
|
+
print_panel("\n".join(seed_info), title="Seed Phrase Decoded")
|
1791
|
+
|
1792
|
+
# Security: Clear the plaintext seed phrase from memory
|
1793
|
+
# This is a best-effort approach, as Python's garbage collection may still keep copies
|
1794
|
+
seed_phrase = None
|
1692
1795
|
|
1693
1796
|
return 0
|
1694
1797
|
else:
|
1695
|
-
|
1798
|
+
error("Failed to decrypt seed phrase")
|
1696
1799
|
return 1
|
1697
1800
|
|
1698
1801
|
except Exception as e:
|
1699
|
-
|
1802
|
+
error(f"Error decrypting seed phrase: {e}")
|
1700
1803
|
|
1701
1804
|
if "decryption failed" in str(e).lower():
|
1702
|
-
|
1805
|
+
warning("Incorrect password")
|
1703
1806
|
|
1704
1807
|
return 1
|
1705
1808
|
|
1706
1809
|
except Exception as e:
|
1707
|
-
|
1810
|
+
error(f"{e}")
|
1708
1811
|
return 1
|
1709
1812
|
|
1710
1813
|
|
@@ -1725,7 +1828,7 @@ def handle_seed_phrase_status(account_name: Optional[str] = None) -> int:
|
|
1725
1828
|
|
1726
1829
|
account = accounts[account_name]
|
1727
1830
|
has_seed = "seed_phrase" in account
|
1728
|
-
is_encrypted = account.get("
|
1831
|
+
is_encrypted = account.get("seed_phrase_encoded", False)
|
1729
1832
|
is_active = account_name == get_active_account()
|
1730
1833
|
|
1731
1834
|
print("\nAccount Status:")
|
@@ -1746,31 +1849,39 @@ def handle_seed_phrase_status(account_name: Optional[str] = None) -> int:
|
|
1746
1849
|
print(f" Address: Unable to derive (Error: {e})")
|
1747
1850
|
|
1748
1851
|
else:
|
1749
|
-
print("Checking
|
1852
|
+
print("Checking active account seed phrase status")
|
1750
1853
|
|
1751
|
-
#
|
1752
|
-
has_seed = "seed_phrase" in config.get("substrate", {})
|
1753
|
-
is_encrypted = config.get("substrate", {}).get("encrypted", False)
|
1754
|
-
|
1755
|
-
print("\nDefault Seed Phrase Status:")
|
1756
|
-
print(f" Has Seed Phrase: {'Yes' if has_seed else 'No'}")
|
1757
|
-
print(f" Encrypted: {'Yes' if is_encrypted else 'No'}")
|
1758
|
-
|
1759
|
-
if has_seed:
|
1760
|
-
try:
|
1761
|
-
# Try to get the address (will use cached if available)
|
1762
|
-
address = get_account_address()
|
1763
|
-
print(f" Address: {address}")
|
1764
|
-
except Exception as e:
|
1765
|
-
if is_encrypted:
|
1766
|
-
print(" Address: Encrypted (password required to view)")
|
1767
|
-
else:
|
1768
|
-
print(f" Address: Unable to derive (Error: {e})")
|
1769
|
-
|
1770
|
-
# Show active account
|
1854
|
+
# Get the active account
|
1771
1855
|
active_account = get_active_account()
|
1772
1856
|
if active_account:
|
1773
|
-
|
1857
|
+
accounts = config.get("substrate", {}).get("accounts", {})
|
1858
|
+
if active_account in accounts:
|
1859
|
+
account = accounts[active_account]
|
1860
|
+
has_seed = "seed_phrase" in account
|
1861
|
+
is_encrypted = account.get("seed_phrase_encoded", False)
|
1862
|
+
|
1863
|
+
print(f"\nActive Account: {active_account}")
|
1864
|
+
print(f" Has Seed Phrase: {'Yes' if has_seed else 'No'}")
|
1865
|
+
print(f" Encrypted: {'Yes' if is_encrypted else 'No'}")
|
1866
|
+
|
1867
|
+
if has_seed:
|
1868
|
+
try:
|
1869
|
+
# Try to get the address (will use cached if available)
|
1870
|
+
address = get_account_address(active_account)
|
1871
|
+
print(f" Address: {address}")
|
1872
|
+
except Exception as e:
|
1873
|
+
if is_encrypted:
|
1874
|
+
print(
|
1875
|
+
" Address: Encrypted (password required to view)"
|
1876
|
+
)
|
1877
|
+
else:
|
1878
|
+
print(f" Address: Unable to derive (Error: {e})")
|
1879
|
+
else:
|
1880
|
+
print(
|
1881
|
+
f"\nActive account '{active_account}' not found in configuration"
|
1882
|
+
)
|
1883
|
+
else:
|
1884
|
+
print("\nNo active account set")
|
1774
1885
|
|
1775
1886
|
return 0
|
1776
1887
|
|
@@ -1784,6 +1895,88 @@ def handle_seed_phrase_status(account_name: Optional[str] = None) -> int:
|
|
1784
1895
|
#
|
1785
1896
|
|
1786
1897
|
|
1898
|
+
def handle_account_info(account_name: Optional[str] = None) -> int:
|
1899
|
+
"""Handle the account info command - displays detailed information about an account"""
|
1900
|
+
try:
|
1901
|
+
# Load configuration
|
1902
|
+
config = load_config()
|
1903
|
+
|
1904
|
+
# If account name not specified, use active account
|
1905
|
+
if not account_name:
|
1906
|
+
account_name = config.get("substrate", {}).get("active_account")
|
1907
|
+
if not account_name:
|
1908
|
+
error("No account specified and no active account")
|
1909
|
+
return 1
|
1910
|
+
|
1911
|
+
# Check if account exists
|
1912
|
+
accounts = config.get("substrate", {}).get("accounts", {})
|
1913
|
+
if account_name not in accounts:
|
1914
|
+
error(f"Account '{account_name}' not found")
|
1915
|
+
return 1
|
1916
|
+
|
1917
|
+
# Get account details
|
1918
|
+
account = accounts[account_name]
|
1919
|
+
has_seed = "seed_phrase" in account
|
1920
|
+
is_encrypted = account.get("seed_phrase_encoded", False)
|
1921
|
+
is_active = account_name == get_active_account()
|
1922
|
+
ss58_address = account.get("ss58_address", "")
|
1923
|
+
|
1924
|
+
# Account information panel with rich formatting
|
1925
|
+
account_info = [
|
1926
|
+
f"Account Name: [bold]{account_name}[/bold]",
|
1927
|
+
f"Active: [bold cyan]{'Yes' if is_active else 'No'}[/bold cyan]",
|
1928
|
+
f"Has Seed Phrase: [bold]{'Yes' if has_seed else 'No'}[/bold]",
|
1929
|
+
f"Encryption: [bold {'green' if is_encrypted else 'yellow'}]{'Encrypted' if is_encrypted else 'Unencrypted'}[/bold {'green' if is_encrypted else 'yellow'}]",
|
1930
|
+
]
|
1931
|
+
|
1932
|
+
if ss58_address:
|
1933
|
+
account_info.append(f"SS58 Address: [bold cyan]{ss58_address}[/bold cyan]")
|
1934
|
+
elif has_seed:
|
1935
|
+
if is_encrypted:
|
1936
|
+
account_info.append(
|
1937
|
+
"[dim]Address: Encrypted (password required to view)[/dim]"
|
1938
|
+
)
|
1939
|
+
else:
|
1940
|
+
try:
|
1941
|
+
# Try to get the address
|
1942
|
+
address = get_account_address(account_name)
|
1943
|
+
account_info.append(
|
1944
|
+
f"SS58 Address: [bold cyan]{address}[/bold cyan]"
|
1945
|
+
)
|
1946
|
+
except Exception as e:
|
1947
|
+
account_info.append(
|
1948
|
+
f"[yellow]Unable to derive address: {e}[/yellow]"
|
1949
|
+
)
|
1950
|
+
|
1951
|
+
# Add suggestions based on account status
|
1952
|
+
account_info.append("")
|
1953
|
+
if is_active:
|
1954
|
+
account_info.append("[bold green]This is your active account[/bold green]")
|
1955
|
+
else:
|
1956
|
+
account_info.append(
|
1957
|
+
f"[dim]To use this account: [bold green underline]hippius account switch {account_name}[/bold green underline][/dim]"
|
1958
|
+
)
|
1959
|
+
|
1960
|
+
if has_seed and not is_encrypted:
|
1961
|
+
account_info.append(
|
1962
|
+
f"[bold yellow underline]WARNING:[/bold yellow underline] Seed phrase is not encrypted"
|
1963
|
+
)
|
1964
|
+
account_info.append(
|
1965
|
+
f"[dim]To encrypt: [bold green underline]hippius account encode --name {account_name}[/bold green underline][/dim]"
|
1966
|
+
)
|
1967
|
+
|
1968
|
+
# Print the panel with rich formatting
|
1969
|
+
print_panel(
|
1970
|
+
"\n".join(account_info), title=f"Account Information: {account_name}"
|
1971
|
+
)
|
1972
|
+
|
1973
|
+
return 0
|
1974
|
+
|
1975
|
+
except Exception as e:
|
1976
|
+
error(f"Error getting account info: {e}")
|
1977
|
+
return 1
|
1978
|
+
|
1979
|
+
|
1787
1980
|
def handle_account_create(
|
1788
1981
|
client: HippiusClient, name: str, encrypt: bool = False
|
1789
1982
|
) -> int:
|
@@ -1797,6 +1990,9 @@ def handle_account_create(
|
|
1797
1990
|
|
1798
1991
|
print(f"Creating new account: {name}")
|
1799
1992
|
|
1993
|
+
# Import Keypair at the beginning to ensure it's available
|
1994
|
+
from substrateinterface import Keypair
|
1995
|
+
|
1800
1996
|
# Generate a new keypair (seed phrase)
|
1801
1997
|
seed_phrase = client.substrate_client.generate_seed_phrase()
|
1802
1998
|
|
@@ -1820,56 +2016,70 @@ def handle_account_create(
|
|
1820
2016
|
return 1
|
1821
2017
|
|
1822
2018
|
# Set the seed phrase for the new account
|
1823
|
-
|
2019
|
+
# First load the config to directly edit it
|
2020
|
+
config = load_config()
|
1824
2021
|
|
1825
|
-
#
|
1826
|
-
|
2022
|
+
# Ensure accounts structure exists
|
2023
|
+
if "accounts" not in config["substrate"]:
|
2024
|
+
config["substrate"]["accounts"] = {}
|
1827
2025
|
|
1828
|
-
#
|
1829
|
-
|
1830
|
-
|
1831
|
-
# Create a temporary client with the password to get the address
|
1832
|
-
temp_client = HippiusClient(
|
1833
|
-
substrate_seed_phrase=seed_phrase,
|
1834
|
-
seed_phrase_password=password,
|
1835
|
-
account_name=name,
|
1836
|
-
)
|
1837
|
-
address = temp_client.substrate_client.get_account_address()
|
1838
|
-
else:
|
1839
|
-
address = get_account_address(name)
|
2026
|
+
# Create keypair directly from seed phrase
|
2027
|
+
keypair = Keypair.create_from_mnemonic(seed_phrase)
|
2028
|
+
address = keypair.ss58_address
|
1840
2029
|
|
1841
|
-
|
1842
|
-
|
1843
|
-
|
1844
|
-
|
1845
|
-
|
1846
|
-
|
2030
|
+
# Add the new account
|
2031
|
+
config["substrate"]["accounts"][name] = {
|
2032
|
+
"seed_phrase": seed_phrase,
|
2033
|
+
"seed_phrase_encoded": False,
|
2034
|
+
"seed_phrase_salt": None,
|
2035
|
+
"ss58_address": address,
|
2036
|
+
}
|
1847
2037
|
|
1848
|
-
|
1849
|
-
|
1850
|
-
print("\nYour seed phrase is encrypted.")
|
1851
|
-
print(
|
1852
|
-
"You'll need to provide the password whenever using this account."
|
1853
|
-
)
|
1854
|
-
else:
|
1855
|
-
print("\nWARNING: Your seed phrase is stored unencrypted.")
|
1856
|
-
print(
|
1857
|
-
"Consider encrypting it with: hippius account encode --name", name
|
1858
|
-
)
|
2038
|
+
# Set as active account
|
2039
|
+
config["substrate"]["active_account"] = name
|
1859
2040
|
|
1860
|
-
|
2041
|
+
# Save the config
|
2042
|
+
save_config(config)
|
1861
2043
|
|
1862
|
-
|
2044
|
+
# Print account information using rich formatting
|
2045
|
+
account_info = [
|
2046
|
+
f"Account: [bold]{name}[/bold]",
|
2047
|
+
f"Address: [bold cyan]{address}[/bold cyan]",
|
2048
|
+
f"Seed phrase: [bold yellow]{seed_phrase}[/bold yellow]",
|
2049
|
+
"",
|
2050
|
+
"[bold red underline]IMPORTANT:[/bold red underline] Keep your seed phrase safe. It's the only way to recover your account!",
|
2051
|
+
]
|
1863
2052
|
|
1864
|
-
|
1865
|
-
|
1866
|
-
|
1867
|
-
|
2053
|
+
# Add encryption status
|
2054
|
+
if encrypt:
|
2055
|
+
account_info.append("")
|
2056
|
+
account_info.append(
|
2057
|
+
"[bold green]Your seed phrase is encrypted.[/bold green]"
|
1868
2058
|
)
|
1869
|
-
|
2059
|
+
account_info.append(
|
2060
|
+
"You'll need to provide the password whenever using this account."
|
2061
|
+
)
|
2062
|
+
else:
|
2063
|
+
account_info.append("")
|
2064
|
+
account_info.append(
|
2065
|
+
"[bold yellow underline]WARNING:[/bold yellow underline] Your seed phrase is stored unencrypted."
|
2066
|
+
)
|
2067
|
+
account_info.append(
|
2068
|
+
f"[bold green underline]Consider encrypting it with: hippius account encode --name {name}[/bold green underline]"
|
2069
|
+
)
|
2070
|
+
|
2071
|
+
account_info.append("")
|
2072
|
+
account_info.append(
|
2073
|
+
"This account is now active. Use it with: [bold]hippius <command>[/bold]"
|
2074
|
+
)
|
2075
|
+
|
2076
|
+
# Print the panel with rich formatting
|
2077
|
+
print_panel("\n".join(account_info), title="Account Created Successfully")
|
2078
|
+
|
2079
|
+
return 0
|
1870
2080
|
|
1871
2081
|
except Exception as e:
|
1872
|
-
|
2082
|
+
error(f"Error creating account: {e}")
|
1873
2083
|
return 1
|
1874
2084
|
|
1875
2085
|
|
@@ -2105,7 +2315,7 @@ def handle_account_list() -> int:
|
|
2105
2315
|
|
2106
2316
|
is_active = account_name == active_account
|
2107
2317
|
has_seed = "seed_phrase" in account_data
|
2108
|
-
is_encrypted = account_data.get("
|
2318
|
+
is_encrypted = account_data.get("seed_phrase_encoded", False)
|
2109
2319
|
|
2110
2320
|
# Get address
|
2111
2321
|
address = account_data.get("ss58_address", "")
|
@@ -2136,8 +2346,8 @@ def handle_account_list() -> int:
|
|
2136
2346
|
|
2137
2347
|
# Instructions
|
2138
2348
|
help_text = [
|
2139
|
-
"To switch accounts: [bold]hippius account switch <account_name>[/bold]",
|
2140
|
-
"To create a new account: [bold]hippius account create --name <account_name>[/bold]",
|
2349
|
+
"To switch accounts: [bold green underline]hippius account switch <account_name>[/bold green underline]",
|
2350
|
+
"To create a new account: [bold green underline]hippius account create --name <account_name>[/bold green underline]",
|
2141
2351
|
]
|
2142
2352
|
print_panel("\n".join(help_text), title="Account Management")
|
2143
2353
|
|
@@ -2188,6 +2398,199 @@ def handle_account_switch(account_name: str) -> int:
|
|
2188
2398
|
return 1
|
2189
2399
|
|
2190
2400
|
|
2401
|
+
def handle_account_login() -> int:
|
2402
|
+
"""Handle the account login command - prompts for account details and creates an account"""
|
2403
|
+
try:
|
2404
|
+
# Display the login banner
|
2405
|
+
from hippius_sdk.cli_assets import LOGIN_ASSET
|
2406
|
+
|
2407
|
+
console.print(LOGIN_ASSET, style="bold cyan")
|
2408
|
+
console.print(
|
2409
|
+
"\n[bold blue]Welcome to Hippius![/bold blue] Let's set up your account.\n"
|
2410
|
+
)
|
2411
|
+
|
2412
|
+
# Create a style for prompts
|
2413
|
+
prompt_style = "bold green"
|
2414
|
+
input_style = "bold cyan"
|
2415
|
+
|
2416
|
+
# Prompt for account name with nice formatting
|
2417
|
+
console.print(
|
2418
|
+
"[bold]Step 1:[/bold] Choose a name for your account", style=prompt_style
|
2419
|
+
)
|
2420
|
+
console.print(
|
2421
|
+
"This name will be used to identify your account in the Hippius system.",
|
2422
|
+
style="dim",
|
2423
|
+
)
|
2424
|
+
console.print("Account name:", style=input_style, end=" ")
|
2425
|
+
name = input().strip()
|
2426
|
+
|
2427
|
+
if not name:
|
2428
|
+
error("[bold red]Account name cannot be empty[/bold red]")
|
2429
|
+
return 1
|
2430
|
+
|
2431
|
+
# Check if account already exists
|
2432
|
+
accounts = list_accounts()
|
2433
|
+
if name in accounts:
|
2434
|
+
warning(f"Account '[bold]{name}[/bold]' already exists")
|
2435
|
+
console.print(
|
2436
|
+
"Do you want to overwrite it? (y/n):", style=input_style, end=" "
|
2437
|
+
)
|
2438
|
+
confirm = input().strip().lower()
|
2439
|
+
if confirm != "y":
|
2440
|
+
info("Login cancelled")
|
2441
|
+
return 0
|
2442
|
+
|
2443
|
+
# Prompt for seed phrase with detailed explanation
|
2444
|
+
console.print(
|
2445
|
+
"\n[bold]Step 2:[/bold] Enter your seed phrase", style=prompt_style
|
2446
|
+
)
|
2447
|
+
console.print(
|
2448
|
+
"Your seed phrase gives access to your blockchain account and funds.",
|
2449
|
+
style="dim",
|
2450
|
+
)
|
2451
|
+
console.print(
|
2452
|
+
"[yellow]Important:[/yellow] Must be 12 or 24 words separated by spaces.",
|
2453
|
+
style="dim",
|
2454
|
+
)
|
2455
|
+
console.print("Seed phrase:", style=input_style, end=" ")
|
2456
|
+
seed_phrase = input().strip()
|
2457
|
+
|
2458
|
+
# Validate the seed phrase
|
2459
|
+
if not seed_phrase or len(seed_phrase.split()) not in [12, 24]:
|
2460
|
+
error(
|
2461
|
+
"[bold red]Invalid seed phrase[/bold red] - must be 12 or 24 words separated by spaces"
|
2462
|
+
)
|
2463
|
+
return 1
|
2464
|
+
|
2465
|
+
# Prompt for encryption with security explanation
|
2466
|
+
console.print("\n[bold]Step 3:[/bold] Secure your account", style=prompt_style)
|
2467
|
+
console.print(
|
2468
|
+
"Encrypting your seed phrase adds an extra layer of security.", style="dim"
|
2469
|
+
)
|
2470
|
+
console.print(
|
2471
|
+
"[bold yellow]Strongly recommended[/bold yellow] to protect your account.",
|
2472
|
+
style="dim",
|
2473
|
+
)
|
2474
|
+
console.print(
|
2475
|
+
"Encrypt seed phrase? [bold green](Y/n)[/bold green]:",
|
2476
|
+
style=input_style,
|
2477
|
+
end=" ",
|
2478
|
+
)
|
2479
|
+
encrypt_input = input().strip().lower()
|
2480
|
+
encrypt = encrypt_input == "y" or encrypt_input == "" or encrypt_input == "yes"
|
2481
|
+
|
2482
|
+
# Set up encryption if requested
|
2483
|
+
password = None
|
2484
|
+
if encrypt:
|
2485
|
+
console.print(
|
2486
|
+
"\n[bold]Step 4:[/bold] Set encryption password", style=prompt_style
|
2487
|
+
)
|
2488
|
+
console.print(
|
2489
|
+
"This password will be required whenever you use your account for blockchain operations.",
|
2490
|
+
style="dim",
|
2491
|
+
)
|
2492
|
+
|
2493
|
+
password = getpass.getpass("Enter a password: ")
|
2494
|
+
confirm = getpass.getpass("Confirm password: ")
|
2495
|
+
|
2496
|
+
if password != confirm:
|
2497
|
+
error("[bold red]Passwords do not match[/bold red]")
|
2498
|
+
return 1
|
2499
|
+
|
2500
|
+
if not password:
|
2501
|
+
error("[bold red]Password cannot be empty for encryption[/bold red]")
|
2502
|
+
return 1
|
2503
|
+
|
2504
|
+
# Initialize address variable
|
2505
|
+
address = None
|
2506
|
+
|
2507
|
+
# Create and store the account
|
2508
|
+
with console.status("[cyan]Setting up your account...[/cyan]", spinner="dots"):
|
2509
|
+
# First, directly modify the config to ensure account is created
|
2510
|
+
config = load_config()
|
2511
|
+
|
2512
|
+
# Ensure accounts structure exists
|
2513
|
+
if "substrate" not in config:
|
2514
|
+
config["substrate"] = {}
|
2515
|
+
if "accounts" not in config["substrate"]:
|
2516
|
+
config["substrate"]["accounts"] = {}
|
2517
|
+
|
2518
|
+
# Create keypair and get address from seed phrase
|
2519
|
+
from substrateinterface import Keypair
|
2520
|
+
|
2521
|
+
keypair = Keypair.create_from_mnemonic(seed_phrase)
|
2522
|
+
address = keypair.ss58_address
|
2523
|
+
|
2524
|
+
# Add the new account
|
2525
|
+
config["substrate"]["accounts"][name] = {
|
2526
|
+
"seed_phrase": seed_phrase,
|
2527
|
+
"seed_phrase_encoded": False,
|
2528
|
+
"seed_phrase_salt": None,
|
2529
|
+
"ss58_address": address,
|
2530
|
+
}
|
2531
|
+
|
2532
|
+
# Set as active account
|
2533
|
+
config["substrate"]["active_account"] = name
|
2534
|
+
|
2535
|
+
# Save the config first
|
2536
|
+
save_config(config)
|
2537
|
+
|
2538
|
+
# Now encrypt if requested
|
2539
|
+
if encrypt:
|
2540
|
+
encrypt_seed_phrase(seed_phrase, password, name)
|
2541
|
+
|
2542
|
+
time.sleep(0.5) # Small delay for visual feedback
|
2543
|
+
|
2544
|
+
# Success panel with account information
|
2545
|
+
account_info = [
|
2546
|
+
f"[bold]Account Name:[/bold] [bold magenta]{name}[/bold magenta]",
|
2547
|
+
f"[bold]Blockchain Address:[/bold] [bold cyan]{address}[/bold cyan]",
|
2548
|
+
"",
|
2549
|
+
"[bold green]✓ Login successful![/bold green]",
|
2550
|
+
"[bold green]✓ Account set as active[/bold green]",
|
2551
|
+
]
|
2552
|
+
|
2553
|
+
if encrypt:
|
2554
|
+
account_info.append("[bold green]✓ Seed phrase encrypted[/bold green]")
|
2555
|
+
account_info.append("")
|
2556
|
+
account_info.append(
|
2557
|
+
"[dim]You'll need your password when using this account for blockchain operations.[/dim]"
|
2558
|
+
)
|
2559
|
+
else:
|
2560
|
+
account_info.append(
|
2561
|
+
"[bold yellow]⚠ Seed phrase not encrypted[/bold yellow]"
|
2562
|
+
)
|
2563
|
+
account_info.append("")
|
2564
|
+
account_info.append(
|
2565
|
+
"[dim]For better security, consider encrypting your seed phrase:[/dim]"
|
2566
|
+
)
|
2567
|
+
account_info.append(
|
2568
|
+
f"[dim] [bold green underline]hippius account encode --name {name}[/bold green underline][/dim]"
|
2569
|
+
)
|
2570
|
+
|
2571
|
+
# Add next steps
|
2572
|
+
account_info.append("")
|
2573
|
+
account_info.append("[bold blue]Next steps:[/bold blue]")
|
2574
|
+
account_info.append(
|
2575
|
+
"• [bold green underline]hippius credits[/bold green underline] - Check your account balance"
|
2576
|
+
)
|
2577
|
+
account_info.append(
|
2578
|
+
"• [bold green underline]hippius files[/bold green underline] - View your stored files"
|
2579
|
+
)
|
2580
|
+
account_info.append(
|
2581
|
+
"• [bold green underline]hippius store <file>[/bold green underline] - Upload a file to IPFS"
|
2582
|
+
)
|
2583
|
+
|
2584
|
+
print_panel(
|
2585
|
+
"\n".join(account_info), title="[bold green]Account Ready[/bold green]"
|
2586
|
+
)
|
2587
|
+
return 0
|
2588
|
+
|
2589
|
+
except Exception as e:
|
2590
|
+
error(f"[bold red]Error logging in:[/bold red] {e}")
|
2591
|
+
return 1
|
2592
|
+
|
2593
|
+
|
2191
2594
|
def handle_account_delete(account_name: str) -> int:
|
2192
2595
|
"""Handle the account delete command"""
|
2193
2596
|
try:
|