hippius 0.2.14__py3-none-any.whl → 0.2.16__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.14.dist-info → hippius-0.2.16.dist-info}/METADATA +3 -1
- hippius-0.2.16.dist-info/RECORD +18 -0
- hippius_sdk/__init__.py +1 -1
- hippius_sdk/cli.py +1 -1
- hippius_sdk/cli_parser.py +26 -2
- hippius_sdk/client.py +23 -1
- hippius_sdk/config.py +4 -0
- hippius_sdk/ipfs.py +189 -0
- hippius_sdk/key_storage.py +281 -0
- hippius_sdk/substrate.py +11 -24
- hippius-0.2.14.dist-info/RECORD +0 -17
- {hippius-0.2.14.dist-info → hippius-0.2.16.dist-info}/WHEEL +0 -0
- {hippius-0.2.14.dist-info → hippius-0.2.16.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.16
|
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
|
@@ -15,6 +15,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
16
16
|
Classifier: Programming Language :: Python :: 3.9
|
17
17
|
Provides-Extra: clipboard
|
18
|
+
Provides-Extra: key-storage
|
19
|
+
Requires-Dist: asyncpg (>=0.29.0,<0.30.0) ; extra == "key-storage"
|
18
20
|
Requires-Dist: base58 (>=2.1.1,<3.0.0)
|
19
21
|
Requires-Dist: cryptography (>=44.0.0,<45.0.0)
|
20
22
|
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
hippius_sdk/__init__.py,sha256=_IVrnzwEOPA6XHWY3wyp07kK2Y761ScXvBRTlqqxF_E,1392
|
2
|
+
hippius_sdk/cli.py,sha256=aqKOYSBSWt7UhcpFt7wf9yIPJ3bznpsJ6ehOnuZ4usI,18235
|
3
|
+
hippius_sdk/cli_assets.py,sha256=V3MX63QTiex6mCp0VDXQJ7cagm5v1s4xtsu8c1O4G_k,371
|
4
|
+
hippius_sdk/cli_handlers.py,sha256=TQNE9os87gRzRKLEO-SIwhFnBtEFMiaSESv-Bu7omfo,128909
|
5
|
+
hippius_sdk/cli_parser.py,sha256=z7UvgWvvy04ey-R56qZiCqYc_9RaNq1rVDkQyXoK3JU,21100
|
6
|
+
hippius_sdk/cli_rich.py,sha256=_jTBYMdHi2--fIVwoeNi-EtkdOb6Zy_O2TUiGvU3O7s,7324
|
7
|
+
hippius_sdk/client.py,sha256=W1aVyvgMXRXVqjwSmRl03kZKB96PHMPcpq3cj1equ-8,20179
|
8
|
+
hippius_sdk/config.py,sha256=Hf_aUYzG9ylzqauA_ABUSSB5mBTYbp-VtB36VQt2XDw,21981
|
9
|
+
hippius_sdk/errors.py,sha256=LScJJmawVAx7aRzqqQguYSkf9iazSjEQEBNlD_GXZ6Y,1589
|
10
|
+
hippius_sdk/ipfs.py,sha256=by45dH1c2IxzkfU7B9-ZRkI5gwEAxE0bUB9KUhszlL8,84460
|
11
|
+
hippius_sdk/ipfs_core.py,sha256=eOOgLoyP9mvwndnCjldnTc7z94ImYCXY3nm7JU3e_Mo,12676
|
12
|
+
hippius_sdk/key_storage.py,sha256=7q5SL3Ls6Ho0xEwYSBpdci7e-bBMJnUMz2j7ROEBSVc,8947
|
13
|
+
hippius_sdk/substrate.py,sha256=AqfQNl5Qv_s6roOESSdwleX0-VGyu5sh3m5-dYZp5ek,49079
|
14
|
+
hippius_sdk/utils.py,sha256=rJ611yvwKSyiBpYU3w-SuyQxoghMGU-ePuslrPv5H5g,7388
|
15
|
+
hippius-0.2.16.dist-info/METADATA,sha256=OMMWMEVBJ-QoTPjHgD0aCVimfs_y_ByECpmK11CbMMw,30088
|
16
|
+
hippius-0.2.16.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
17
|
+
hippius-0.2.16.dist-info/entry_points.txt,sha256=b1lo60zRXmv1ud-c5BC-cJcAfGE5FD4qM_nia6XeQtM,98
|
18
|
+
hippius-0.2.16.dist-info/RECORD,,
|
hippius_sdk/__init__.py
CHANGED
hippius_sdk/cli.py
CHANGED
hippius_sdk/cli_parser.py
CHANGED
@@ -34,6 +34,9 @@ examples:
|
|
34
34
|
# Store a file
|
35
35
|
hippius store example.txt
|
36
36
|
|
37
|
+
# Add a file (alias for store)
|
38
|
+
hippius add example.txt
|
39
|
+
|
37
40
|
# Store a directory
|
38
41
|
hippius store-dir ./my_directory
|
39
42
|
|
@@ -78,6 +81,10 @@ examples:
|
|
78
81
|
|
79
82
|
# Delete an erasure-coded file and all its chunks
|
80
83
|
hippius ec-delete QmMetadataHash
|
84
|
+
|
85
|
+
# Configure PostgreSQL key storage
|
86
|
+
hippius config set key_storage database_url 'postgresql://user:pass@localhost:5432/hippius_keys'
|
87
|
+
hippius config set key_storage enabled true
|
81
88
|
""",
|
82
89
|
)
|
83
90
|
|
@@ -214,6 +221,23 @@ def add_storage_commands(subparsers):
|
|
214
221
|
help="Don't publish file to IPFS or store on the blockchain (local only)",
|
215
222
|
)
|
216
223
|
|
224
|
+
# Add command (alias for store)
|
225
|
+
add_parser = subparsers.add_parser(
|
226
|
+
"add",
|
227
|
+
help="Upload a file to IPFS and store it on Substrate (alias for 'store')",
|
228
|
+
)
|
229
|
+
add_parser.add_argument("file_path", help="Path to file to upload")
|
230
|
+
add_parser.add_argument(
|
231
|
+
"--publish",
|
232
|
+
action="store_true",
|
233
|
+
help="Publish file to IPFS and store on the blockchain (default)",
|
234
|
+
)
|
235
|
+
add_parser.add_argument(
|
236
|
+
"--no-publish",
|
237
|
+
action="store_true",
|
238
|
+
help="Don't publish file to IPFS or store on the blockchain (local only)",
|
239
|
+
)
|
240
|
+
|
217
241
|
# Store directory command
|
218
242
|
store_dir_parser = subparsers.add_parser(
|
219
243
|
"store-dir", help="Upload a directory to IPFS and store all files on Substrate"
|
@@ -420,7 +444,7 @@ def add_config_commands(subparsers):
|
|
420
444
|
get_parser = config_subparsers.add_parser("get", help="Get a configuration value")
|
421
445
|
get_parser.add_argument(
|
422
446
|
"section",
|
423
|
-
help="Configuration section (ipfs, substrate, encryption, erasure_coding, cli)",
|
447
|
+
help="Configuration section (ipfs, substrate, encryption, erasure_coding, cli, key_storage)",
|
424
448
|
)
|
425
449
|
get_parser.add_argument("key", help="Configuration key")
|
426
450
|
|
@@ -428,7 +452,7 @@ def add_config_commands(subparsers):
|
|
428
452
|
set_parser = config_subparsers.add_parser("set", help="Set a configuration value")
|
429
453
|
set_parser.add_argument(
|
430
454
|
"section",
|
431
|
-
help="Configuration section (ipfs, substrate, encryption, erasure_coding, cli)",
|
455
|
+
help="Configuration section (ipfs, substrate, encryption, erasure_coding, cli, key_storage)",
|
432
456
|
)
|
433
457
|
set_parser.add_argument("key", help="Configuration key")
|
434
458
|
set_parser.add_argument("value", help="Configuration value")
|
hippius_sdk/client.py
CHANGED
@@ -9,7 +9,7 @@ import nacl.secret
|
|
9
9
|
import nacl.utils
|
10
10
|
|
11
11
|
from hippius_sdk.config import get_config_value, get_encryption_key
|
12
|
-
from hippius_sdk.ipfs import IPFSClient
|
12
|
+
from hippius_sdk.ipfs import IPFSClient, S3PublishResult
|
13
13
|
from hippius_sdk.substrate import SubstrateClient
|
14
14
|
|
15
15
|
|
@@ -510,3 +510,25 @@ class HippiusClient:
|
|
510
510
|
parallel_limit,
|
511
511
|
seed_phrase=seed_phrase,
|
512
512
|
)
|
513
|
+
|
514
|
+
async def s3_publish(
|
515
|
+
self, file_path: str, encrypt: bool, seed_phrase: str
|
516
|
+
) -> S3PublishResult:
|
517
|
+
"""
|
518
|
+
Publish a file to IPFS and the Hippius marketplace in one operation.
|
519
|
+
|
520
|
+
Args:
|
521
|
+
file_path: Path to the file to publish
|
522
|
+
encrypt: Whether to encrypt the file before uploading
|
523
|
+
seed_phrase: Seed phrase for blockchain transaction signing
|
524
|
+
|
525
|
+
Returns:
|
526
|
+
S3PublishResult: Object containing CID, file info, and transaction hash
|
527
|
+
|
528
|
+
Raises:
|
529
|
+
HippiusIPFSError: If IPFS operations (add or pin) fail
|
530
|
+
HippiusSubstrateError: If substrate call fails
|
531
|
+
FileNotFoundError: If the file doesn't exist
|
532
|
+
ValueError: If encryption is requested but not available
|
533
|
+
"""
|
534
|
+
return await self.ipfs_client.s3_publish(file_path, encrypt, seed_phrase)
|
hippius_sdk/config.py
CHANGED
hippius_sdk/ipfs.py
CHANGED
@@ -2,8 +2,10 @@
|
|
2
2
|
IPFS operations for the Hippius SDK.
|
3
3
|
"""
|
4
4
|
import asyncio
|
5
|
+
import base64
|
5
6
|
import hashlib
|
6
7
|
import json
|
8
|
+
import logging
|
7
9
|
import os
|
8
10
|
import random
|
9
11
|
import shutil
|
@@ -13,8 +15,10 @@ import uuid
|
|
13
15
|
from typing import Any, Callable, Dict, List, Optional
|
14
16
|
|
15
17
|
import httpx
|
18
|
+
from pydantic import BaseModel
|
16
19
|
|
17
20
|
from hippius_sdk.config import get_config_value, get_encryption_key
|
21
|
+
from hippius_sdk.errors import HippiusIPFSError, HippiusSubstrateError
|
18
22
|
from hippius_sdk.ipfs_core import AsyncIPFSClient
|
19
23
|
from hippius_sdk.substrate import FileInput, SubstrateClient
|
20
24
|
from hippius_sdk.utils import format_cid, format_size
|
@@ -43,6 +47,20 @@ PARALLEL_ORIGINAL_CHUNKS = (
|
|
43
47
|
)
|
44
48
|
|
45
49
|
|
50
|
+
class S3PublishResult(BaseModel):
|
51
|
+
"""Result model for s3_publish method."""
|
52
|
+
|
53
|
+
cid: str
|
54
|
+
file_name: str
|
55
|
+
size_bytes: int
|
56
|
+
encryption_key: Optional[str]
|
57
|
+
tx_hash: str
|
58
|
+
|
59
|
+
|
60
|
+
# Set up logger for this module
|
61
|
+
logger = logging.getLogger(__name__)
|
62
|
+
|
63
|
+
|
46
64
|
class IPFSClient:
|
47
65
|
"""Client for interacting with IPFS."""
|
48
66
|
|
@@ -513,6 +531,9 @@ class IPFSClient:
|
|
513
531
|
raise RuntimeError(f"Failed to download directory: {str(e)}")
|
514
532
|
else:
|
515
533
|
# Regular file download
|
534
|
+
# Create parent directories if they don't exist
|
535
|
+
os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
|
536
|
+
|
516
537
|
retries = 0
|
517
538
|
while retries < max_retries:
|
518
539
|
try:
|
@@ -1890,3 +1911,171 @@ class IPFSClient:
|
|
1890
1911
|
# In either case, we report success
|
1891
1912
|
print("Delete EC file operation completed successfully")
|
1892
1913
|
return True
|
1914
|
+
|
1915
|
+
async def s3_publish(
|
1916
|
+
self, file_path: str, encrypt: bool, seed_phrase: str
|
1917
|
+
) -> S3PublishResult:
|
1918
|
+
"""
|
1919
|
+
Publish a file to IPFS and the Hippius marketplace in one operation.
|
1920
|
+
|
1921
|
+
This method automatically manages encryption keys per seed phrase:
|
1922
|
+
- If encrypt=True, it will get or generate an encryption key for the seed phrase
|
1923
|
+
- Keys are stored in PostgreSQL and versioned (never deleted)
|
1924
|
+
- Always uses the most recent key for a seed phrase
|
1925
|
+
|
1926
|
+
Args:
|
1927
|
+
file_path: Path to the file to publish
|
1928
|
+
encrypt: Whether to encrypt the file before uploading
|
1929
|
+
seed_phrase: Seed phrase for blockchain transaction signing
|
1930
|
+
|
1931
|
+
Returns:
|
1932
|
+
S3PublishResult: Object containing CID, file info, and transaction hash
|
1933
|
+
|
1934
|
+
Raises:
|
1935
|
+
HippiusIPFSError: If IPFS operations (add or pin) fail
|
1936
|
+
HippiusSubstrateError: If substrate call fails
|
1937
|
+
FileNotFoundError: If the file doesn't exist
|
1938
|
+
ValueError: If encryption is requested but not available
|
1939
|
+
"""
|
1940
|
+
# Check if file exists and get initial info
|
1941
|
+
if not os.path.exists(file_path):
|
1942
|
+
raise FileNotFoundError(f"File {file_path} not found")
|
1943
|
+
|
1944
|
+
# Get file info
|
1945
|
+
filename = os.path.basename(file_path)
|
1946
|
+
size_bytes = os.path.getsize(file_path)
|
1947
|
+
|
1948
|
+
# Handle encryption if requested with automatic key management
|
1949
|
+
encryption_key_used = None
|
1950
|
+
if encrypt:
|
1951
|
+
# Check if key storage is enabled and available
|
1952
|
+
key_storage_available = False
|
1953
|
+
try:
|
1954
|
+
from hippius_sdk.key_storage import (
|
1955
|
+
generate_and_store_key_for_seed,
|
1956
|
+
get_key_for_seed,
|
1957
|
+
is_key_storage_enabled,
|
1958
|
+
)
|
1959
|
+
|
1960
|
+
key_storage_available = is_key_storage_enabled()
|
1961
|
+
logger.debug(f"Key storage enabled: {key_storage_available}")
|
1962
|
+
except ImportError:
|
1963
|
+
logger.debug("Key storage module not available")
|
1964
|
+
key_storage_available = False
|
1965
|
+
|
1966
|
+
if key_storage_available:
|
1967
|
+
# Use PostgreSQL-backed key storage
|
1968
|
+
logger.info(f"Using PostgreSQL key storage for seed phrase")
|
1969
|
+
|
1970
|
+
# Try to get existing key for this seed phrase
|
1971
|
+
existing_key_b64 = await get_key_for_seed(seed_phrase)
|
1972
|
+
|
1973
|
+
if existing_key_b64:
|
1974
|
+
# Use existing key
|
1975
|
+
logger.debug("Using existing encryption key for seed phrase")
|
1976
|
+
encryption_key_bytes = base64.b64decode(existing_key_b64)
|
1977
|
+
encryption_key_used = existing_key_b64
|
1978
|
+
else:
|
1979
|
+
# Generate and store new key for this seed phrase
|
1980
|
+
logger.info("Generating new encryption key for seed phrase")
|
1981
|
+
new_key_b64 = await generate_and_store_key_for_seed(seed_phrase)
|
1982
|
+
encryption_key_bytes = base64.b64decode(new_key_b64)
|
1983
|
+
encryption_key_used = new_key_b64
|
1984
|
+
|
1985
|
+
# Read file content into memory
|
1986
|
+
with open(file_path, "rb") as f:
|
1987
|
+
file_data = f.read()
|
1988
|
+
|
1989
|
+
# Encrypt the data using the key from key storage
|
1990
|
+
import nacl.secret
|
1991
|
+
|
1992
|
+
box = nacl.secret.SecretBox(encryption_key_bytes)
|
1993
|
+
encrypted_data = box.encrypt(file_data)
|
1994
|
+
|
1995
|
+
# Overwrite the original file with encrypted data
|
1996
|
+
with open(file_path, "wb") as f:
|
1997
|
+
f.write(encrypted_data)
|
1998
|
+
else:
|
1999
|
+
# Fallback to the original encryption system if key_storage is not available
|
2000
|
+
if not self.encryption_available:
|
2001
|
+
raise ValueError(
|
2002
|
+
"Encryption requested but not available. Either install key storage with 'pip install hippius_sdk[key_storage]' or configure an encryption key with 'hippius keygen --save'"
|
2003
|
+
)
|
2004
|
+
|
2005
|
+
# Read file content into memory
|
2006
|
+
with open(file_path, "rb") as f:
|
2007
|
+
file_data = f.read()
|
2008
|
+
|
2009
|
+
# Encrypt the data using the client's encryption key
|
2010
|
+
encrypted_data = self.encrypt_data(file_data)
|
2011
|
+
|
2012
|
+
# Overwrite the original file with encrypted data
|
2013
|
+
with open(file_path, "wb") as f:
|
2014
|
+
f.write(encrypted_data)
|
2015
|
+
|
2016
|
+
# Store the encryption key for the result
|
2017
|
+
encryption_key_used = (
|
2018
|
+
base64.b64encode(self.encryption_key).decode("utf-8")
|
2019
|
+
if self.encryption_key
|
2020
|
+
else None
|
2021
|
+
)
|
2022
|
+
|
2023
|
+
# Add file to IPFS
|
2024
|
+
try:
|
2025
|
+
result = await self.client.add_file(file_path)
|
2026
|
+
cid = result["Hash"]
|
2027
|
+
except Exception as e:
|
2028
|
+
raise HippiusIPFSError(f"Failed to add file to IPFS: {str(e)}")
|
2029
|
+
|
2030
|
+
# Pin the file to IPFS
|
2031
|
+
try:
|
2032
|
+
pin_result = await self.client.pin(cid)
|
2033
|
+
except Exception as e:
|
2034
|
+
raise HippiusIPFSError(f"Failed to pin file to IPFS: {str(e)}")
|
2035
|
+
|
2036
|
+
# Publish to substrate marketplace
|
2037
|
+
try:
|
2038
|
+
# Pass the seed phrase directly to avoid password prompts for encrypted config
|
2039
|
+
substrate_client = SubstrateClient(seed_phrase=seed_phrase)
|
2040
|
+
logger.info(
|
2041
|
+
f"Submitting storage request to substrate for file: {filename}, CID: {cid}"
|
2042
|
+
)
|
2043
|
+
|
2044
|
+
tx_hash = await substrate_client.storage_request(
|
2045
|
+
files=[
|
2046
|
+
FileInput(
|
2047
|
+
file_hash=cid,
|
2048
|
+
file_name=filename,
|
2049
|
+
)
|
2050
|
+
],
|
2051
|
+
miner_ids=[],
|
2052
|
+
seed_phrase=seed_phrase,
|
2053
|
+
)
|
2054
|
+
|
2055
|
+
logger.debug(f"Substrate call result: {tx_hash}")
|
2056
|
+
|
2057
|
+
# Check if we got a valid transaction hash
|
2058
|
+
if not tx_hash or tx_hash == "0x" or len(tx_hash) < 10:
|
2059
|
+
logger.error(f"Invalid transaction hash received: {tx_hash}")
|
2060
|
+
raise HippiusSubstrateError(
|
2061
|
+
f"Invalid transaction hash received: {tx_hash}. This might indicate insufficient credits or transaction failure."
|
2062
|
+
)
|
2063
|
+
|
2064
|
+
logger.info(
|
2065
|
+
f"Successfully published to substrate with transaction: {tx_hash}"
|
2066
|
+
)
|
2067
|
+
|
2068
|
+
except Exception as e:
|
2069
|
+
logger.error(f"Substrate call failed: {str(e)}")
|
2070
|
+
logger.debug(
|
2071
|
+
"Possible causes: insufficient credits, network issues, invalid seed phrase, or substrate node unavailability"
|
2072
|
+
)
|
2073
|
+
raise HippiusSubstrateError(f"Failed to publish to substrate: {str(e)}")
|
2074
|
+
|
2075
|
+
return S3PublishResult(
|
2076
|
+
cid=cid,
|
2077
|
+
file_name=filename,
|
2078
|
+
size_bytes=size_bytes,
|
2079
|
+
encryption_key=encryption_key_used,
|
2080
|
+
tx_hash=tx_hash,
|
2081
|
+
)
|
@@ -0,0 +1,281 @@
|
|
1
|
+
"""
|
2
|
+
Key storage module for managing encryption keys per seed phrase.
|
3
|
+
|
4
|
+
This module provides PostgreSQL-backed storage for:
|
5
|
+
1. Base64 encoded seed phrases
|
6
|
+
2. Encryption keys associated with each seed phrase (versioned, never deleted)
|
7
|
+
"""
|
8
|
+
|
9
|
+
import base64
|
10
|
+
import hashlib
|
11
|
+
import os
|
12
|
+
from datetime import datetime
|
13
|
+
from typing import Optional
|
14
|
+
|
15
|
+
from hippius_sdk.config import get_config_value
|
16
|
+
|
17
|
+
# Import asyncpg with fallback
|
18
|
+
try:
|
19
|
+
import asyncpg
|
20
|
+
|
21
|
+
ASYNCPG_AVAILABLE = True
|
22
|
+
except ImportError:
|
23
|
+
ASYNCPG_AVAILABLE = False
|
24
|
+
|
25
|
+
|
26
|
+
class KeyStorageError(Exception):
|
27
|
+
"""Base exception for key storage operations."""
|
28
|
+
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
class KeyStorage:
|
33
|
+
"""PostgreSQL-backed key storage for seed phrases and encryption keys."""
|
34
|
+
|
35
|
+
def __init__(self, database_url: Optional[str] = None):
|
36
|
+
"""
|
37
|
+
Initialize key storage with database connection.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
database_url: PostgreSQL connection URL. If None, uses config or defaults to localhost.
|
41
|
+
"""
|
42
|
+
if not ASYNCPG_AVAILABLE:
|
43
|
+
raise KeyStorageError(
|
44
|
+
"asyncpg is required for key storage. Install it with: pip install 'hippius_sdk[key_storage]'"
|
45
|
+
)
|
46
|
+
|
47
|
+
if database_url is None:
|
48
|
+
database_url = get_config_value(
|
49
|
+
"key_storage",
|
50
|
+
"database_url",
|
51
|
+
"postgresql://postgres:password@localhost:5432/hippius_keys",
|
52
|
+
)
|
53
|
+
|
54
|
+
self.database_url = database_url
|
55
|
+
|
56
|
+
async def _get_connection(self):
|
57
|
+
"""Get a database connection."""
|
58
|
+
try:
|
59
|
+
return await asyncpg.connect(self.database_url)
|
60
|
+
except Exception as e:
|
61
|
+
raise KeyStorageError(f"Failed to connect to database: {e}")
|
62
|
+
|
63
|
+
async def _ensure_tables_exist(self):
|
64
|
+
"""Create tables if they don't exist."""
|
65
|
+
create_seed_phrases_table = """
|
66
|
+
CREATE TABLE IF NOT EXISTS seed_phrases (
|
67
|
+
id SERIAL PRIMARY KEY,
|
68
|
+
seed_hash VARCHAR(64) UNIQUE NOT NULL,
|
69
|
+
seed_phrase_b64 TEXT NOT NULL,
|
70
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
71
|
+
);
|
72
|
+
"""
|
73
|
+
|
74
|
+
create_encryption_keys_table = """
|
75
|
+
CREATE TABLE IF NOT EXISTS encryption_keys (
|
76
|
+
id SERIAL PRIMARY KEY,
|
77
|
+
seed_hash VARCHAR(64) NOT NULL,
|
78
|
+
encryption_key_b64 TEXT NOT NULL,
|
79
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
80
|
+
FOREIGN KEY (seed_hash) REFERENCES seed_phrases(seed_hash)
|
81
|
+
);
|
82
|
+
"""
|
83
|
+
|
84
|
+
create_index = """
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_encryption_keys_seed_hash_created
|
86
|
+
ON encryption_keys(seed_hash, created_at DESC);
|
87
|
+
"""
|
88
|
+
|
89
|
+
try:
|
90
|
+
conn = await self._get_connection()
|
91
|
+
try:
|
92
|
+
await conn.execute(create_seed_phrases_table)
|
93
|
+
await conn.execute(create_encryption_keys_table)
|
94
|
+
await conn.execute(create_index)
|
95
|
+
finally:
|
96
|
+
await conn.close()
|
97
|
+
except Exception as e:
|
98
|
+
raise KeyStorageError(f"Failed to create tables: {e}")
|
99
|
+
|
100
|
+
def _hash_seed_phrase(self, seed_phrase: str) -> str:
|
101
|
+
"""Create a SHA-256 hash of the seed phrase for indexing."""
|
102
|
+
return hashlib.sha256(seed_phrase.encode("utf-8")).hexdigest()
|
103
|
+
|
104
|
+
async def _ensure_seed_phrase_exists(self, seed_phrase: str) -> str:
|
105
|
+
"""Ensure seed phrase exists in database and return its hash."""
|
106
|
+
seed_hash = self._hash_seed_phrase(seed_phrase)
|
107
|
+
seed_phrase_b64 = base64.b64encode(seed_phrase.encode("utf-8")).decode("utf-8")
|
108
|
+
|
109
|
+
try:
|
110
|
+
conn = await self._get_connection()
|
111
|
+
try:
|
112
|
+
# Try to insert, ignore if already exists
|
113
|
+
await conn.execute(
|
114
|
+
"""
|
115
|
+
INSERT INTO seed_phrases (seed_hash, seed_phrase_b64)
|
116
|
+
VALUES ($1, $2)
|
117
|
+
ON CONFLICT (seed_hash) DO NOTHING
|
118
|
+
""",
|
119
|
+
seed_hash,
|
120
|
+
seed_phrase_b64,
|
121
|
+
)
|
122
|
+
finally:
|
123
|
+
await conn.close()
|
124
|
+
return seed_hash
|
125
|
+
except Exception as e:
|
126
|
+
raise KeyStorageError(f"Failed to store seed phrase: {e}")
|
127
|
+
|
128
|
+
async def set_key_for_seed(self, seed_phrase: str, encryption_key_b64: str) -> None:
|
129
|
+
"""
|
130
|
+
Store a new encryption key for a seed phrase.
|
131
|
+
|
132
|
+
Creates a new row (doesn't update existing ones) to maintain key history.
|
133
|
+
|
134
|
+
Args:
|
135
|
+
seed_phrase: The seed phrase
|
136
|
+
encryption_key_b64: Base64-encoded encryption key
|
137
|
+
|
138
|
+
Raises:
|
139
|
+
KeyStorageError: If storage fails
|
140
|
+
"""
|
141
|
+
await self._ensure_tables_exist()
|
142
|
+
seed_hash = await self._ensure_seed_phrase_exists(seed_phrase)
|
143
|
+
|
144
|
+
try:
|
145
|
+
conn = await self._get_connection()
|
146
|
+
try:
|
147
|
+
await conn.execute(
|
148
|
+
"""
|
149
|
+
INSERT INTO encryption_keys (seed_hash, encryption_key_b64)
|
150
|
+
VALUES ($1, $2)
|
151
|
+
""",
|
152
|
+
seed_hash,
|
153
|
+
encryption_key_b64,
|
154
|
+
)
|
155
|
+
finally:
|
156
|
+
await conn.close()
|
157
|
+
except Exception as e:
|
158
|
+
raise KeyStorageError(f"Failed to store encryption key: {e}")
|
159
|
+
|
160
|
+
async def get_key_for_seed(self, seed_phrase: str) -> Optional[str]:
|
161
|
+
"""
|
162
|
+
Get the most recent encryption key for a seed phrase.
|
163
|
+
|
164
|
+
Args:
|
165
|
+
seed_phrase: The seed phrase
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
Base64-encoded encryption key or None if not found
|
169
|
+
|
170
|
+
Raises:
|
171
|
+
KeyStorageError: If database operation fails
|
172
|
+
"""
|
173
|
+
await self._ensure_tables_exist()
|
174
|
+
seed_hash = self._hash_seed_phrase(seed_phrase)
|
175
|
+
|
176
|
+
try:
|
177
|
+
conn = await self._get_connection()
|
178
|
+
try:
|
179
|
+
result = await conn.fetchrow(
|
180
|
+
"""
|
181
|
+
SELECT encryption_key_b64
|
182
|
+
FROM encryption_keys
|
183
|
+
WHERE seed_hash = $1
|
184
|
+
ORDER BY created_at DESC
|
185
|
+
LIMIT 1
|
186
|
+
""",
|
187
|
+
seed_hash,
|
188
|
+
)
|
189
|
+
|
190
|
+
return result["encryption_key_b64"] if result else None
|
191
|
+
finally:
|
192
|
+
await conn.close()
|
193
|
+
except Exception as e:
|
194
|
+
raise KeyStorageError(f"Failed to retrieve encryption key: {e}")
|
195
|
+
|
196
|
+
async def generate_and_store_key_for_seed(self, seed_phrase: str) -> str:
|
197
|
+
"""
|
198
|
+
Generate a new encryption key and store it for the seed phrase.
|
199
|
+
|
200
|
+
Args:
|
201
|
+
seed_phrase: The seed phrase
|
202
|
+
|
203
|
+
Returns:
|
204
|
+
Base64-encoded encryption key that was generated and stored
|
205
|
+
|
206
|
+
Raises:
|
207
|
+
KeyStorageError: If generation or storage fails
|
208
|
+
"""
|
209
|
+
# Generate a new encryption key
|
210
|
+
try:
|
211
|
+
import nacl.secret
|
212
|
+
import nacl.utils
|
213
|
+
|
214
|
+
# Generate a random key
|
215
|
+
key = nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)
|
216
|
+
key_b64 = base64.b64encode(key).decode("utf-8")
|
217
|
+
|
218
|
+
# Store it
|
219
|
+
await self.set_key_for_seed(seed_phrase, key_b64)
|
220
|
+
|
221
|
+
return key_b64
|
222
|
+
except ImportError:
|
223
|
+
raise KeyStorageError(
|
224
|
+
"PyNaCl is required for key generation. Install it with: pip install pynacl"
|
225
|
+
)
|
226
|
+
except Exception as e:
|
227
|
+
raise KeyStorageError(f"Failed to generate encryption key: {e}")
|
228
|
+
|
229
|
+
|
230
|
+
# Module-level convenience functions
|
231
|
+
_default_storage = None
|
232
|
+
|
233
|
+
|
234
|
+
def is_key_storage_enabled() -> bool:
|
235
|
+
"""Check if key storage is enabled in configuration."""
|
236
|
+
return get_config_value("key_storage", "enabled", False)
|
237
|
+
|
238
|
+
|
239
|
+
def get_default_storage() -> KeyStorage:
|
240
|
+
"""Get the default KeyStorage instance."""
|
241
|
+
global _default_storage
|
242
|
+
if _default_storage is None:
|
243
|
+
_default_storage = KeyStorage()
|
244
|
+
return _default_storage
|
245
|
+
|
246
|
+
|
247
|
+
async def get_key_for_seed(seed_phrase: str) -> Optional[str]:
|
248
|
+
"""
|
249
|
+
Get the most recent encryption key for a seed phrase.
|
250
|
+
|
251
|
+
Args:
|
252
|
+
seed_phrase: The seed phrase
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
Base64-encoded encryption key or None if not found
|
256
|
+
"""
|
257
|
+
return await get_default_storage().get_key_for_seed(seed_phrase)
|
258
|
+
|
259
|
+
|
260
|
+
async def set_key_for_seed(seed_phrase: str, encryption_key_b64: str) -> None:
|
261
|
+
"""
|
262
|
+
Store a new encryption key for a seed phrase.
|
263
|
+
|
264
|
+
Args:
|
265
|
+
seed_phrase: The seed phrase
|
266
|
+
encryption_key_b64: Base64-encoded encryption key
|
267
|
+
"""
|
268
|
+
return await get_default_storage().set_key_for_seed(seed_phrase, encryption_key_b64)
|
269
|
+
|
270
|
+
|
271
|
+
async def generate_and_store_key_for_seed(seed_phrase: str) -> str:
|
272
|
+
"""
|
273
|
+
Generate a new encryption key and store it for the seed phrase.
|
274
|
+
|
275
|
+
Args:
|
276
|
+
seed_phrase: The seed phrase
|
277
|
+
|
278
|
+
Returns:
|
279
|
+
Base64-encoded encryption key that was generated and stored
|
280
|
+
"""
|
281
|
+
return await get_default_storage().generate_and_store_key_for_seed(seed_phrase)
|
hippius_sdk/substrate.py
CHANGED
@@ -69,6 +69,7 @@ class SubstrateClient:
|
|
69
69
|
url: Optional[str] = None,
|
70
70
|
password: Optional[str] = None,
|
71
71
|
account_name: Optional[str] = None,
|
72
|
+
seed_phrase: Optional[str] = None,
|
72
73
|
):
|
73
74
|
"""
|
74
75
|
Initialize the Substrate client.
|
@@ -77,6 +78,7 @@ class SubstrateClient:
|
|
77
78
|
url: WebSocket URL of the Hippius substrate node (from config if None)
|
78
79
|
password: Optional password to decrypt the seed phrase if it's encrypted
|
79
80
|
account_name: Optional name of the account to use (uses active account if None)
|
81
|
+
seed_phrase: Optional unencrypted seed phrase to use directly (bypasses config)
|
80
82
|
"""
|
81
83
|
# Load configuration values if not explicitly provided
|
82
84
|
if url is None:
|
@@ -96,8 +98,7 @@ class SubstrateClient:
|
|
96
98
|
if addr:
|
97
99
|
self._account_address = addr
|
98
100
|
|
99
|
-
|
100
|
-
self._seed_phrase = None
|
101
|
+
self._seed_phrase = seed_phrase # Use passed seed phrase if provided
|
101
102
|
|
102
103
|
# Don't connect immediately to avoid exceptions during initialization
|
103
104
|
# Connection will happen lazily when needed
|
@@ -595,33 +596,19 @@ class SubstrateClient:
|
|
595
596
|
}
|
596
597
|
|
597
598
|
# Create the call to the marketplace
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
)
|
604
|
-
except Exception as e:
|
605
|
-
print(f"Warning: Error composing call: {e}")
|
606
|
-
print("Attempting to use IpfsPallet.storeFile instead...")
|
607
|
-
|
608
|
-
# Try with IpfsPallet.storeFile as an alternative
|
609
|
-
alt_call_params = {
|
610
|
-
"fileHash": files_list_cid,
|
611
|
-
"fileName": f"files_list_{uuid.uuid4()}", # Generate a unique ID
|
612
|
-
}
|
613
|
-
call = self._substrate.compose_call(
|
614
|
-
call_module="IpfsPallet",
|
615
|
-
call_function="storeFile",
|
616
|
-
call_params=alt_call_params,
|
617
|
-
)
|
599
|
+
call = self._substrate.compose_call(
|
600
|
+
call_module="Marketplace",
|
601
|
+
call_function="storage_request",
|
602
|
+
call_params=call_params,
|
603
|
+
)
|
618
604
|
|
619
605
|
# Get payment info to estimate the fee
|
620
606
|
payment_info = self._substrate.get_payment_info(
|
621
|
-
call=call,
|
607
|
+
call=call,
|
608
|
+
keypair=self._keypair,
|
622
609
|
)
|
623
610
|
|
624
|
-
print(f"Payment info: {json.dumps(payment_info, indent=2)}")
|
611
|
+
print(f"]Payment info: {json.dumps(payment_info, indent=2)}")
|
625
612
|
|
626
613
|
# Convert partialFee from Substrate (10^18 units) to a more readable format
|
627
614
|
estimated_fee = payment_info.get("partialFee", 0)
|
hippius-0.2.14.dist-info/RECORD
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
hippius_sdk/__init__.py,sha256=MWFJq4fNljQXF2VRer9p01xKPsQLjrVgRNyuQc6xf8Q,1392
|
2
|
-
hippius_sdk/cli.py,sha256=pzwoa-X5cwdA_pM-fqUyXZKHgcZODrLe4qHZuCqQMtQ,18210
|
3
|
-
hippius_sdk/cli_assets.py,sha256=V3MX63QTiex6mCp0VDXQJ7cagm5v1s4xtsu8c1O4G_k,371
|
4
|
-
hippius_sdk/cli_handlers.py,sha256=TQNE9os87gRzRKLEO-SIwhFnBtEFMiaSESv-Bu7omfo,128909
|
5
|
-
hippius_sdk/cli_parser.py,sha256=Qh2wgkFBUTPldvGoTQuoNKQl5Vo0x6fPEsPBU5oymP4,20242
|
6
|
-
hippius_sdk/cli_rich.py,sha256=_jTBYMdHi2--fIVwoeNi-EtkdOb6Zy_O2TUiGvU3O7s,7324
|
7
|
-
hippius_sdk/client.py,sha256=TSfEQ-8Yq-4Dc0FZR9HNvDQ-CTzvCh7n8D1wdFwv-kc,19289
|
8
|
-
hippius_sdk/config.py,sha256=wVzhVIBtijatVG7MZ3HvAwdsz_-arkTvBf5NUiQHNTo,21841
|
9
|
-
hippius_sdk/errors.py,sha256=LScJJmawVAx7aRzqqQguYSkf9iazSjEQEBNlD_GXZ6Y,1589
|
10
|
-
hippius_sdk/ipfs.py,sha256=mxdovvcs5WjXD-b2A4tTfCF1G-kR6BSxskCCYlSmdMs,76937
|
11
|
-
hippius_sdk/ipfs_core.py,sha256=eOOgLoyP9mvwndnCjldnTc7z94ImYCXY3nm7JU3e_Mo,12676
|
12
|
-
hippius_sdk/substrate.py,sha256=lp-GF2qfZgD_XwZwc2UNNkp-AkGUpOiZQ5aJUiea8VA,49608
|
13
|
-
hippius_sdk/utils.py,sha256=rJ611yvwKSyiBpYU3w-SuyQxoghMGU-ePuslrPv5H5g,7388
|
14
|
-
hippius-0.2.14.dist-info/METADATA,sha256=obIQW4DLtFF2EtTviTw4u-A5sfA6rPOiPd0ZfurSabU,29993
|
15
|
-
hippius-0.2.14.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
16
|
-
hippius-0.2.14.dist-info/entry_points.txt,sha256=b1lo60zRXmv1ud-c5BC-cJcAfGE5FD4qM_nia6XeQtM,98
|
17
|
-
hippius-0.2.14.dist-info/RECORD,,
|
File without changes
|
File without changes
|