hippius 0.1.6__py3-none-any.whl → 0.1.9__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_sdk/substrate.py CHANGED
@@ -6,9 +6,17 @@ Note: This functionality is coming soon and not implemented yet.
6
6
 
7
7
  import os
8
8
  import json
9
+ import uuid
9
10
  from typing import Dict, Any, Optional, List, Union
10
11
  from substrateinterface import SubstrateInterface, Keypair
11
12
  from dotenv import load_dotenv
13
+ from hippius_sdk.config import (
14
+ get_config_value,
15
+ get_seed_phrase,
16
+ set_seed_phrase,
17
+ get_account_address,
18
+ get_active_account,
19
+ )
12
20
 
13
21
  # Load environment variables
14
22
  load_dotenv()
@@ -37,32 +45,51 @@ class SubstrateClient:
37
45
  """
38
46
  Client for interacting with the Hippius Substrate blockchain.
39
47
 
48
+ Provides functionality for storage requests and other blockchain operations.
40
49
  Note: This functionality is not fully implemented yet and is under active development.
41
50
  """
42
51
 
43
- def __init__(self, url: str = None, seed_phrase: Optional[str] = None):
52
+ def __init__(
53
+ self,
54
+ url: Optional[str] = None,
55
+ seed_phrase: Optional[str] = None,
56
+ password: Optional[str] = None,
57
+ account_name: Optional[str] = None,
58
+ ):
44
59
  """
45
60
  Initialize the Substrate client.
46
61
 
47
62
  Args:
48
- url: WebSocket URL of the Hippius substrate node
49
- If not provided, uses SUBSTRATE_URL from environment
50
- seed_phrase: Seed phrase for the account (mnemonic)
51
- If not provided, uses SUBSTRATE_SEED_PHRASE from environment
63
+ url: WebSocket URL of the Hippius substrate node (from config if None)
64
+ seed_phrase: Seed phrase for the account (mnemonic) (from config if None)
65
+ password: Optional password to decrypt the seed phrase if it's encrypted
66
+ account_name: Optional name of the account to use (uses active account if None)
52
67
  """
53
- if not url:
54
- url = os.getenv("SUBSTRATE_URL", "wss://rpc.hippius.network")
68
+ # Load configuration values if not explicitly provided
69
+ if url is None:
70
+ url = get_config_value("substrate", "url", "wss://rpc.hippius.network")
55
71
 
56
72
  # Store URL and initialize variables
57
73
  self.url = url
58
74
  self._substrate = None
59
75
  self._keypair = None
76
+ self._account_name = account_name or get_active_account()
77
+ self._account_address = None
78
+ self._read_only = False
79
+
80
+ # Get the account address for read-only operations
81
+ addr = get_account_address(self._account_name)
82
+ if addr:
83
+ self._account_address = addr
60
84
 
61
- # Set seed phrase if provided or available in environment
85
+ # Set seed phrase if provided or available in configuration
62
86
  if seed_phrase:
63
87
  self.set_seed_phrase(seed_phrase)
64
- elif os.getenv("SUBSTRATE_SEED_PHRASE"):
65
- self.set_seed_phrase(os.getenv("SUBSTRATE_SEED_PHRASE"))
88
+ else:
89
+ # Only try to get the seed phrase if we need it for the current operation
90
+ # We'll defer this to when it's actually needed
91
+ self._seed_phrase = None
92
+ self._seed_phrase_password = password
66
93
 
67
94
  # Don't connect immediately to avoid exceptions during initialization
68
95
  # Connection will happen lazily when needed
@@ -84,11 +111,19 @@ class SubstrateClient:
84
111
  # Only create keypair if seed phrase is available
85
112
  if hasattr(self, "_seed_phrase") and self._seed_phrase:
86
113
  self._keypair = Keypair.create_from_mnemonic(self._seed_phrase)
114
+ self._account_address = self._keypair.ss58_address
87
115
  print(
88
116
  f"Connected successfully. Account address: {self._keypair.ss58_address}"
89
117
  )
118
+ self._read_only = False
119
+ elif self._account_address:
120
+ print(
121
+ f"Connected successfully in read-only mode. Account address: {self._account_address}"
122
+ )
123
+ self._read_only = True
90
124
  else:
91
- print("Connected successfully (read-only mode, no keypair)")
125
+ print("Connected successfully (read-only mode, no account)")
126
+ self._read_only = True
92
127
 
93
128
  return True
94
129
 
@@ -100,6 +135,48 @@ class SubstrateClient:
100
135
 
101
136
  return False
102
137
 
138
+ def _ensure_keypair(self) -> bool:
139
+ """
140
+ Ensure we have a keypair for signing transactions.
141
+ Will prompt for password if needed.
142
+
143
+ Returns:
144
+ bool: True if keypair is available, False if it couldn't be created
145
+ """
146
+ if self._keypair:
147
+ return True
148
+
149
+ # If we have a seed phrase, create the keypair
150
+ if hasattr(self, "_seed_phrase") and self._seed_phrase:
151
+ try:
152
+ self._keypair = Keypair.create_from_mnemonic(self._seed_phrase)
153
+ self._account_address = self._keypair.ss58_address
154
+ print(f"Keypair created for account: {self._keypair.ss58_address}")
155
+ self._read_only = False
156
+ return True
157
+ except Exception as e:
158
+ print(f"Warning: Could not create keypair from seed phrase: {e}")
159
+ return False
160
+
161
+ # Otherwise, try to get the seed phrase from config
162
+ try:
163
+ config_seed = get_seed_phrase(
164
+ self._seed_phrase_password, self._account_name
165
+ )
166
+ if config_seed:
167
+ self._seed_phrase = config_seed
168
+ self._keypair = Keypair.create_from_mnemonic(self._seed_phrase)
169
+ self._account_address = self._keypair.ss58_address
170
+ print(f"Keypair created for account: {self._keypair.ss58_address}")
171
+ self._read_only = False
172
+ return True
173
+ else:
174
+ print("No seed phrase available. Cannot sign transactions.")
175
+ return False
176
+ except Exception as e:
177
+ print(f"Warning: Could not get seed phrase from config: {e}")
178
+ return False
179
+
103
180
  def set_seed_phrase(self, seed_phrase: str) -> None:
104
181
  """
105
182
  Set or update the seed phrase used for signing transactions.
@@ -110,17 +187,15 @@ class SubstrateClient:
110
187
  if not seed_phrase or not seed_phrase.strip():
111
188
  raise ValueError("Seed phrase cannot be empty")
112
189
 
113
- # Store the seed phrase
190
+ # Store the seed phrase in memory for this session
114
191
  self._seed_phrase = seed_phrase.strip()
192
+ self._read_only = False
115
193
 
116
194
  # Try to create the keypair if possible
117
195
  try:
118
- if hasattr(self, "_substrate") and self._substrate:
119
- # If we already have a connection, create the keypair
120
- self._keypair = Keypair.create_from_mnemonic(self._seed_phrase)
121
- print(f"Keypair created for account: {self._keypair.ss58_address}")
122
- else:
123
- print(f"Seed phrase set (keypair will be created when connecting)")
196
+ self._keypair = Keypair.create_from_mnemonic(self._seed_phrase)
197
+ self._account_address = self._keypair.ss58_address
198
+ print(f"Keypair created for account: {self._keypair.ss58_address}")
124
199
  except Exception as e:
125
200
  print(f"Warning: Could not create keypair from seed phrase: {e}")
126
201
  print(f"Keypair will be created when needed")
@@ -131,8 +206,8 @@ class SubstrateClient:
131
206
  """
132
207
  Submit a storage request for IPFS files to the marketplace.
133
208
 
134
- This method batches all files into a single transaction to efficiently store
135
- multiple files at once.
209
+ This method creates a JSON file with the list of files to pin, uploads it to IPFS,
210
+ and submits the CID of this file to the chain.
136
211
 
137
212
  Args:
138
213
  files: List of FileInput objects or dictionaries with fileHash and fileName
@@ -147,6 +222,10 @@ class SubstrateClient:
147
222
  ... FileInput("QmHash2", "file2.jpg")
148
223
  ... ])
149
224
  """
225
+ # Check if we have a keypair for signing transactions
226
+ if not self._ensure_keypair():
227
+ raise ValueError("Seed phrase must be set before making transactions")
228
+
150
229
  # Convert any dict inputs to FileInput objects
151
230
  file_inputs = []
152
231
  for file in files:
@@ -163,7 +242,7 @@ class SubstrateClient:
163
242
  file_inputs.append(file)
164
243
 
165
244
  # Print what is being submitted
166
- print(f"Submitting storage request for {len(file_inputs)} files as a batch:")
245
+ print(f"Preparing storage request for {len(file_inputs)} files:")
167
246
  for file in file_inputs:
168
247
  print(f" - {file.file_name}: {file.file_hash}")
169
248
 
@@ -183,36 +262,50 @@ class SubstrateClient:
183
262
  )
184
263
  print(f"Connected to Substrate node at {self.url}")
185
264
 
186
- # Create keypair from seed phrase if not already created
187
- if not hasattr(self, "_keypair") or self._keypair is None:
188
- if not hasattr(self, "_seed_phrase") or not self._seed_phrase:
189
- raise ValueError(
190
- "Seed phrase must be set before making transactions"
191
- )
192
-
193
- print("Creating keypair from seed phrase...")
194
- self._keypair = Keypair.create_from_mnemonic(self._seed_phrase)
195
- print(f"Keypair created for address: {self._keypair.ss58_address}")
196
-
197
- # Prepare storage request call
198
- print("Preparing marketplace.storageRequest batch call...")
199
-
200
- # Format files for the batch call - all files are included in a single array
201
- formatted_files = []
265
+ # Step 1: Create a JSON file with the list of files to pin
266
+ file_list = []
202
267
  for file_input in file_inputs:
203
- formatted_files.append(
204
- {
205
- "file_hash": file_input.file_hash,
206
- "file_name": file_input.file_name,
207
- }
268
+ file_list.append(
269
+ {"filename": file_input.file_name, "cid": file_input.file_hash}
208
270
  )
209
271
 
210
- # Create call parameters with all files in a single batch
272
+ # Convert to JSON
273
+ files_json = json.dumps(file_list, indent=2)
274
+ print(f"Created file list with {len(file_list)} entries")
275
+
276
+ # Step 2: Upload the JSON file to IPFS
277
+ import tempfile
278
+ from hippius_sdk.ipfs import IPFSClient
279
+
280
+ ipfs_client = IPFSClient()
281
+
282
+ # Create a temporary file with the JSON content
283
+ with tempfile.NamedTemporaryFile(
284
+ mode="w+", suffix=".json", delete=False
285
+ ) as temp_file:
286
+ temp_file_path = temp_file.name
287
+ temp_file.write(files_json)
288
+
289
+ try:
290
+ print("Uploading file list to IPFS...")
291
+ upload_result = ipfs_client.upload_file(temp_file_path)
292
+ files_list_cid = upload_result["cid"]
293
+ print(f"File list uploaded to IPFS with CID: {files_list_cid}")
294
+ finally:
295
+ # Clean up the temporary file
296
+ if os.path.exists(temp_file_path):
297
+ os.remove(temp_file_path)
298
+
299
+ # Step 3: Submit the CID of the JSON file to the chain
300
+ # Create call parameters with the CID of the JSON file
211
301
  call_params = {
212
- "files_input": formatted_files,
213
- "miner_ids": miner_ids
214
- if miner_ids
215
- else [], # Always include miner_ids, empty array if not specified
302
+ "files_input": [
303
+ {
304
+ "file_hash": files_list_cid,
305
+ "file_name": f"files_list_{uuid.uuid4()}", # Generate a unique ID
306
+ }
307
+ ],
308
+ "miner_ids": miner_ids if miner_ids else [],
216
309
  }
217
310
 
218
311
  # Create the call to the marketplace
@@ -236,7 +329,9 @@ class SubstrateClient:
236
329
  call=call, keypair=self._keypair
237
330
  )
238
331
 
239
- print(f"Submitting batch transaction for {len(formatted_files)} files...")
332
+ print(
333
+ f"Submitting transaction to store {len(file_list)} files via file list CID..."
334
+ )
240
335
 
241
336
  # Submit the transaction
242
337
  response = self._substrate.submit_extrinsic(
@@ -246,11 +341,10 @@ class SubstrateClient:
246
341
  # Get the transaction hash
247
342
  tx_hash = response.extrinsic_hash
248
343
 
249
- print(f"Batch transaction submitted successfully!")
344
+ print(f"Transaction submitted successfully!")
250
345
  print(f"Transaction hash: {tx_hash}")
251
- print(
252
- f"All {len(formatted_files)} files have been stored in a single transaction"
253
- )
346
+ print(f"File list CID: {files_list_cid}")
347
+ print(f"All {len(file_list)} files will be stored through this request")
254
348
 
255
349
  return tx_hash
256
350
 
@@ -315,6 +409,10 @@ class SubstrateClient:
315
409
  Returns:
316
410
  str: Transaction hash
317
411
  """
412
+ # This requires a keypair for signing
413
+ if not self._ensure_keypair():
414
+ raise ValueError("Seed phrase must be set before making transactions")
415
+
318
416
  raise NotImplementedError("Substrate functionality is not implemented yet.")
319
417
 
320
418
  def get_storage_fee(self, file_size_mb: float) -> float:
@@ -369,19 +467,17 @@ class SubstrateClient:
369
467
  )
370
468
  print(f"Connected to Substrate node at {self.url}")
371
469
 
372
- # Use provided account address or default to keypair address
470
+ # Use provided account address or default to keypair/configured address
373
471
  if not account_address:
374
- if not hasattr(self, "_keypair") or self._keypair is None:
375
- if not hasattr(self, "_seed_phrase") or not self._seed_phrase:
376
- raise ValueError(
377
- "No account address provided and no seed phrase is set"
378
- )
379
-
380
- print("Creating keypair from seed phrase to get account address...")
381
- self._keypair = Keypair.create_from_mnemonic(self._seed_phrase)
382
-
383
- account_address = self._keypair.ss58_address
384
- print(f"Using keypair address: {account_address}")
472
+ if self._account_address:
473
+ account_address = self._account_address
474
+ print(f"Using account address: {account_address}")
475
+ else:
476
+ # Try to get the address from the keypair (requires seed phrase)
477
+ if not self._ensure_keypair():
478
+ raise ValueError("No account address available")
479
+ account_address = self._keypair.ss58_address
480
+ print(f"Using keypair address: {account_address}")
385
481
 
386
482
  # Query the blockchain for free credits
387
483
  print(f"Querying free credits for account: {account_address}")
@@ -435,19 +531,17 @@ class SubstrateClient:
435
531
  )
436
532
  print(f"Connected to Substrate node at {self.url}")
437
533
 
438
- # Use provided account address or default to keypair address
534
+ # Use provided account address or default to keypair/configured address
439
535
  if not account_address:
440
- if not hasattr(self, "_keypair") or self._keypair is None:
441
- if not hasattr(self, "_seed_phrase") or not self._seed_phrase:
442
- raise ValueError(
443
- "No account address provided and no seed phrase is set"
444
- )
445
-
446
- print("Creating keypair from seed phrase to get account address...")
447
- self._keypair = Keypair.create_from_mnemonic(self._seed_phrase)
448
-
449
- account_address = self._keypair.ss58_address
450
- print(f"Using keypair address: {account_address}")
536
+ if self._account_address:
537
+ account_address = self._account_address
538
+ print(f"Using account address: {account_address}")
539
+ else:
540
+ # Try to get the address from the keypair (requires seed phrase)
541
+ if not self._ensure_keypair():
542
+ raise ValueError("No account address available")
543
+ account_address = self._keypair.ss58_address
544
+ print(f"Using keypair address: {account_address}")
451
545
 
452
546
  # Query the blockchain for user file hashes
453
547
  print(f"Querying file hashes for account: {account_address}")
@@ -516,19 +610,17 @@ class SubstrateClient:
516
610
  )
517
611
  print(f"Connected to Substrate node at {self.url}")
518
612
 
519
- # Use provided account address or default to keypair address
613
+ # Use provided account address or default to keypair/configured address
520
614
  if not account_address:
521
- if not hasattr(self, "_keypair") or self._keypair is None:
522
- if not hasattr(self, "_seed_phrase") or not self._seed_phrase:
523
- raise ValueError(
524
- "No account address provided and no seed phrase is set"
525
- )
526
-
527
- print("Creating keypair from seed phrase to get account address...")
528
- self._keypair = Keypair.create_from_mnemonic(self._seed_phrase)
529
-
530
- account_address = self._keypair.ss58_address
531
- print(f"Using keypair address: {account_address}")
615
+ if self._account_address:
616
+ account_address = self._account_address
617
+ print(f"Using account address: {account_address}")
618
+ else:
619
+ # Try to get the address from the keypair (requires seed phrase)
620
+ if not self._ensure_keypair():
621
+ raise ValueError("No account address available")
622
+ account_address = self._keypair.ss58_address
623
+ print(f"Using keypair address: {account_address}")
532
624
 
533
625
  # Prepare the JSON-RPC request
534
626
  request = {
@@ -1,9 +0,0 @@
1
- hippius_sdk/__init__.py,sha256=SwOREu9EJZ9ZRM-rSPX0o1hhsOUIADuP3CxoF4Mp_qI,288
2
- hippius_sdk/cli.py,sha256=WfjU9nuUBXN6Tu25PnOpLHftClP_umh6Zl9t4BOzAfo,30576
3
- hippius_sdk/client.py,sha256=bHsoadw2WMMZDU7D0r02nHeU82PAa4cvmblieDzBw54,13305
4
- hippius_sdk/ipfs.py,sha256=C9oMTBefCIfWFUsUBxhUkivz5rIUkhHKJtqdVIkMAbc,61475
5
- hippius_sdk/substrate.py,sha256=mfDxbKn9HdtcK1xEnj_BnnreRw8ITZswtDoBhtliidM,27278
6
- hippius-0.1.6.dist-info/METADATA,sha256=295Uv9mZq1G0pypT4PibEmTDVNRr7gM_ScFNVPZTfdo,16580
7
- hippius-0.1.6.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
8
- hippius-0.1.6.dist-info/entry_points.txt,sha256=b1lo60zRXmv1ud-c5BC-cJcAfGE5FD4qM_nia6XeQtM,98
9
- hippius-0.1.6.dist-info/RECORD,,