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-0.1.6.dist-info → hippius-0.1.9.dist-info}/METADATA +365 -7
- hippius-0.1.9.dist-info/RECORD +10 -0
- hippius_sdk/__init__.py +45 -1
- hippius_sdk/cli.py +1269 -36
- hippius_sdk/client.py +53 -12
- hippius_sdk/config.py +744 -0
- hippius_sdk/ipfs.py +178 -87
- hippius_sdk/substrate.py +180 -88
- hippius-0.1.6.dist-info/RECORD +0 -9
- {hippius-0.1.6.dist-info → hippius-0.1.9.dist-info}/WHEEL +0 -0
- {hippius-0.1.6.dist-info → hippius-0.1.9.dist-info}/entry_points.txt +0 -0
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__(
|
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
|
-
|
50
|
-
|
51
|
-
|
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
|
54
|
-
|
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
|
85
|
+
# Set seed phrase if provided or available in configuration
|
62
86
|
if seed_phrase:
|
63
87
|
self.set_seed_phrase(seed_phrase)
|
64
|
-
|
65
|
-
|
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
|
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
|
-
|
119
|
-
|
120
|
-
|
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
|
135
|
-
|
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"
|
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
|
187
|
-
|
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
|
-
|
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
|
-
#
|
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":
|
213
|
-
|
214
|
-
|
215
|
-
|
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(
|
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"
|
344
|
+
print(f"Transaction submitted successfully!")
|
250
345
|
print(f"Transaction hash: {tx_hash}")
|
251
|
-
print(
|
252
|
-
|
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
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
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
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
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
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
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 = {
|
hippius-0.1.6.dist-info/RECORD
DELETED
@@ -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,,
|
File without changes
|
File without changes
|