hippius 0.2.14__tar.gz → 0.2.16__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hippius
3
- Version: 0.2.14
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)
@@ -26,7 +26,7 @@ from hippius_sdk.config import (
26
26
  from hippius_sdk.ipfs import IPFSClient
27
27
  from hippius_sdk.utils import format_cid, format_size, hex_to_ipfs_cid
28
28
 
29
- __version__ = "0.2.14"
29
+ __version__ = "0.2.16"
30
30
  __all__ = [
31
31
  "HippiusClient",
32
32
  "IPFSClient",
@@ -145,7 +145,7 @@ def main():
145
145
  decrypt=decrypt,
146
146
  )
147
147
 
148
- elif args.command == "store":
148
+ elif args.command == "store" or args.command == "add":
149
149
  return run_async_handler(
150
150
  cli_handlers.handle_store,
151
151
  client,
@@ -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")
@@ -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)
@@ -49,6 +49,10 @@ DEFAULT_CONFIG = {
49
49
  "verbose": False,
50
50
  "max_retries": 3,
51
51
  },
52
+ "key_storage": {
53
+ "database_url": "postgresql://postgres:password@localhost:5432/hippius_keys",
54
+ "enabled": False,
55
+ },
52
56
  }
53
57
 
54
58
 
@@ -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)
@@ -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
- # For backward compatibility - storing the seed phrase is deprecated but needed for older code
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
- try:
599
- call = self._substrate.compose_call(
600
- call_module="Marketplace",
601
- call_function="storage_request",
602
- call_params=call_params,
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, keypair=self._keypair
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)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "hippius"
3
- version = "0.2.14"
3
+ version = "0.2.16"
4
4
  description = "Python SDK and CLI for Hippius blockchain storage"
5
5
  authors = ["Dubs <dubs@dubs.rs>"]
6
6
  readme = "README.md"
@@ -28,12 +28,14 @@ base58 = "^2.1.1"
28
28
  pynacl = "^1.5.0"
29
29
  cryptography = "^44.0.0"
30
30
  pyperclip = {version = "^1.8.2", optional = true}
31
+ asyncpg = {version = "^0.29.0", optional = true}
31
32
  zfec = "^1.5.3"
32
33
  mnemonic = "^0.20"
33
34
  rich = "^14.0.0"
34
35
 
35
36
  [tool.poetry.extras]
36
37
  clipboard = ["pyperclip"]
38
+ key_storage = ["asyncpg"]
37
39
 
38
40
  [tool.poetry.scripts]
39
41
  hippius = "hippius_sdk.cli:main"
File without changes
File without changes
File without changes