hippius 0.1.12__py3-none-any.whl → 0.1.14__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.12.dist-info → hippius-0.1.14.dist-info}/METADATA +1 -148
- hippius-0.1.14.dist-info/RECORD +10 -0
- hippius_sdk/__init__.py +15 -19
- hippius_sdk/cli.py +197 -497
- hippius_sdk/client.py +7 -8
- hippius_sdk/config.py +7 -77
- hippius_sdk/ipfs.py +12 -12
- hippius_sdk/substrate.py +10 -6
- hippius-0.1.12.dist-info/RECORD +0 -12
- hippius_sdk/account.py +0 -648
- hippius_sdk/utils.py +0 -87
- {hippius-0.1.12.dist-info → hippius-0.1.14.dist-info}/WHEEL +0 -0
- {hippius-0.1.12.dist-info → hippius-0.1.14.dist-info}/entry_points.txt +0 -0
hippius_sdk/account.py
DELETED
@@ -1,648 +0,0 @@
|
|
1
|
-
from typing import Dict, List, Optional
|
2
|
-
import json
|
3
|
-
import os
|
4
|
-
import uuid
|
5
|
-
import time
|
6
|
-
|
7
|
-
from substrateinterface import SubstrateInterface, Keypair
|
8
|
-
from substrateinterface.exceptions import SubstrateRequestException
|
9
|
-
|
10
|
-
from hippius_sdk.config import get_config_value, get_keypair
|
11
|
-
from hippius_sdk.utils import ensure_directory_exists
|
12
|
-
|
13
|
-
|
14
|
-
class AccountManager:
|
15
|
-
"""
|
16
|
-
Manages Hippius blockchain accounts including coldkeys, hotkeys, and proxy relationships.
|
17
|
-
"""
|
18
|
-
|
19
|
-
def __init__(self, substrate_interface=None, config_directory=None):
|
20
|
-
"""
|
21
|
-
Initialize the AccountManager.
|
22
|
-
|
23
|
-
Args:
|
24
|
-
substrate_interface: Existing SubstrateInterface or None to create one
|
25
|
-
config_directory: Custom config directory or None for default
|
26
|
-
"""
|
27
|
-
# Initialize substrate interface if not provided
|
28
|
-
if substrate_interface:
|
29
|
-
self.substrate = substrate_interface
|
30
|
-
else:
|
31
|
-
node_url = get_config_value("substrate", "url")
|
32
|
-
self.substrate = SubstrateInterface(url=node_url)
|
33
|
-
|
34
|
-
# Set up config directory
|
35
|
-
self.config_directory = config_directory
|
36
|
-
if not self.config_directory:
|
37
|
-
home_dir = os.path.expanduser("~")
|
38
|
-
self.config_directory = os.path.join(home_dir, ".hippius")
|
39
|
-
|
40
|
-
ensure_directory_exists(self.config_directory)
|
41
|
-
self.accounts_file = os.path.join(self.config_directory, "accounts.json")
|
42
|
-
|
43
|
-
# Initialize or load accounts data
|
44
|
-
self._initialize_accounts_file()
|
45
|
-
|
46
|
-
def _initialize_accounts_file(self):
|
47
|
-
"""Initialize the accounts file if it doesn't exist."""
|
48
|
-
if not os.path.exists(self.accounts_file):
|
49
|
-
with open(self.accounts_file, "w") as f:
|
50
|
-
json.dump(
|
51
|
-
{"coldkeys": {}, "hotkeys": {}, "relationships": []}, f, indent=2
|
52
|
-
)
|
53
|
-
|
54
|
-
def _load_accounts_data(self) -> Dict:
|
55
|
-
"""Load the accounts data from the file."""
|
56
|
-
with open(self.accounts_file, "r") as f:
|
57
|
-
return json.load(f)
|
58
|
-
|
59
|
-
def _save_accounts_data(self, data: Dict):
|
60
|
-
"""Save the accounts data to the file."""
|
61
|
-
with open(self.accounts_file, "w") as f:
|
62
|
-
json.dump(data, f, indent=2)
|
63
|
-
|
64
|
-
def create_coldkey(
|
65
|
-
self,
|
66
|
-
name: str = "hippius_coldkey",
|
67
|
-
mnemonic: Optional[str] = None,
|
68
|
-
encrypt: bool = True,
|
69
|
-
password: Optional[str] = None,
|
70
|
-
) -> Dict:
|
71
|
-
"""
|
72
|
-
Create a new coldkey or import one from a mnemonic.
|
73
|
-
|
74
|
-
Args:
|
75
|
-
name: Name for the coldkey (default: "hippius_coldkey")
|
76
|
-
mnemonic: Optional mnemonic seed phrase to use
|
77
|
-
encrypt: Whether to encrypt the mnemonic with a password
|
78
|
-
password: Optional password (if None and encrypt=True, will prompt)
|
79
|
-
|
80
|
-
Returns:
|
81
|
-
Dict with coldkey information
|
82
|
-
"""
|
83
|
-
# Create keypair
|
84
|
-
if mnemonic:
|
85
|
-
keypair = Keypair.create_from_mnemonic(mnemonic)
|
86
|
-
else:
|
87
|
-
keypair = Keypair.create_from_uri(f"//{name}_{uuid.uuid4()}")
|
88
|
-
# Ensure we capture the mnemonic if generated
|
89
|
-
mnemonic = keypair.mnemonic if hasattr(keypair, "mnemonic") else None
|
90
|
-
|
91
|
-
# Save to accounts.json
|
92
|
-
data = self._load_accounts_data()
|
93
|
-
|
94
|
-
# Initialize coldkey info
|
95
|
-
coldkey_info = {
|
96
|
-
"name": name,
|
97
|
-
"address": keypair.ss58_address,
|
98
|
-
"created_at": time.time(),
|
99
|
-
"public_key": keypair.public_key.hex(),
|
100
|
-
"encrypted": encrypt,
|
101
|
-
}
|
102
|
-
|
103
|
-
# Handle encryption of mnemonic
|
104
|
-
if mnemonic:
|
105
|
-
if encrypt:
|
106
|
-
# Import getpass here to avoid circular imports
|
107
|
-
import getpass
|
108
|
-
|
109
|
-
# Get password if not provided
|
110
|
-
if password is None:
|
111
|
-
password = getpass.getpass("Enter password to encrypt mnemonic: ")
|
112
|
-
password_confirm = getpass.getpass("Confirm password: ")
|
113
|
-
|
114
|
-
if password != password_confirm:
|
115
|
-
raise ValueError("Passwords do not match")
|
116
|
-
|
117
|
-
# Encrypt the mnemonic
|
118
|
-
# We'll encrypt locally and store in our account.json file
|
119
|
-
from hippius_sdk.config import encrypt_with_password
|
120
|
-
|
121
|
-
encrypted_data, salt = encrypt_with_password(mnemonic, password)
|
122
|
-
|
123
|
-
coldkey_info["mnemonic"] = encrypted_data
|
124
|
-
coldkey_info["salt"] = salt
|
125
|
-
|
126
|
-
# Also store in the main config for compatibility
|
127
|
-
from hippius_sdk.config import set_seed_phrase
|
128
|
-
|
129
|
-
set_seed_phrase(
|
130
|
-
seed_phrase=mnemonic,
|
131
|
-
encode=True,
|
132
|
-
password=password,
|
133
|
-
account_name=f"{name}_{keypair.ss58_address}",
|
134
|
-
)
|
135
|
-
|
136
|
-
print(f"Mnemonic encrypted and stored securely.")
|
137
|
-
print(
|
138
|
-
f"You will need your password to sign transactions with this account."
|
139
|
-
)
|
140
|
-
else:
|
141
|
-
# Store without encryption (not recommended)
|
142
|
-
coldkey_info["mnemonic"] = mnemonic
|
143
|
-
print("WARNING: Mnemonic stored without encryption.")
|
144
|
-
print("Consider using --encrypt for better security.")
|
145
|
-
|
146
|
-
# Also store in the main config for compatibility
|
147
|
-
from hippius_sdk.config import set_seed_phrase
|
148
|
-
|
149
|
-
set_seed_phrase(
|
150
|
-
seed_phrase=mnemonic,
|
151
|
-
encode=False,
|
152
|
-
account_name=f"{name}_{keypair.ss58_address}",
|
153
|
-
)
|
154
|
-
|
155
|
-
data["coldkeys"][keypair.ss58_address] = coldkey_info
|
156
|
-
self._save_accounts_data(data)
|
157
|
-
|
158
|
-
# Display information to the user
|
159
|
-
print(f"Coldkey created successfully!")
|
160
|
-
print(f"Name: {name}")
|
161
|
-
print(f"Address: {keypair.ss58_address}")
|
162
|
-
|
163
|
-
return coldkey_info
|
164
|
-
|
165
|
-
def create_hotkey(
|
166
|
-
self, name: Optional[str] = None, coldkey_address: Optional[str] = None
|
167
|
-
) -> Dict:
|
168
|
-
"""
|
169
|
-
Create a new hotkey and associate it with a coldkey.
|
170
|
-
|
171
|
-
Args:
|
172
|
-
name: Optional custom name for the hotkey
|
173
|
-
coldkey_address: SS58 address of the coldkey to associate with.
|
174
|
-
If None, will attempt to use the only coldkey if only one exists,
|
175
|
-
or will raise an error if multiple coldkeys exist.
|
176
|
-
|
177
|
-
Returns:
|
178
|
-
Dict with hotkey information
|
179
|
-
|
180
|
-
Raises:
|
181
|
-
ValueError: If no coldkey is provided and multiple coldkeys exist,
|
182
|
-
or if the specified coldkey doesn't exist
|
183
|
-
"""
|
184
|
-
# Load account data first to check coldkeys
|
185
|
-
data = self._load_accounts_data()
|
186
|
-
|
187
|
-
# Verify we have a valid coldkey to associate with
|
188
|
-
if not coldkey_address:
|
189
|
-
# If no coldkey specified, check if we have only one coldkey
|
190
|
-
available_coldkeys = list(data["coldkeys"].keys())
|
191
|
-
|
192
|
-
if not available_coldkeys:
|
193
|
-
raise ValueError(
|
194
|
-
"No coldkey provided and no coldkeys found. "
|
195
|
-
"Please create a coldkey first with: hippius account coldkey create"
|
196
|
-
)
|
197
|
-
elif len(available_coldkeys) == 1:
|
198
|
-
# If only one coldkey exists, use it automatically
|
199
|
-
coldkey_address = available_coldkeys[0]
|
200
|
-
coldkey_name = data["coldkeys"][coldkey_address]["name"]
|
201
|
-
print(f"Automatically associating with the only available coldkey:")
|
202
|
-
print(f" Name: {coldkey_name}")
|
203
|
-
print(f" Address: {coldkey_address}")
|
204
|
-
else:
|
205
|
-
# If multiple coldkeys exist, we need the user to specify
|
206
|
-
coldkey_list = "\n ".join(
|
207
|
-
[f"{k} ({data['coldkeys'][k]['name']})" for k in available_coldkeys]
|
208
|
-
)
|
209
|
-
raise ValueError(
|
210
|
-
f"No coldkey provided and multiple coldkeys exist. "
|
211
|
-
f"Please specify which coldkey to associate with using --coldkey.\n"
|
212
|
-
f"Available coldkeys:\n {coldkey_list}"
|
213
|
-
)
|
214
|
-
elif coldkey_address not in data["coldkeys"]:
|
215
|
-
raise ValueError(f"Coldkey {coldkey_address} not found")
|
216
|
-
|
217
|
-
# Generate a name if not provided
|
218
|
-
if not name:
|
219
|
-
# Get the next available hotkey number for this coldkey
|
220
|
-
existing_relationships = [
|
221
|
-
r["hotkey"]
|
222
|
-
for r in data["relationships"]
|
223
|
-
if r["coldkey"] == coldkey_address
|
224
|
-
]
|
225
|
-
|
226
|
-
existing_hotkeys = [
|
227
|
-
data["hotkeys"][addr]
|
228
|
-
for addr in existing_relationships
|
229
|
-
if addr in data["hotkeys"]
|
230
|
-
]
|
231
|
-
|
232
|
-
# Use the coldkey's name as a prefix for better organization
|
233
|
-
coldkey_name = data["coldkeys"][coldkey_address]["name"]
|
234
|
-
hotkey_prefix = f"{coldkey_name}_hotkey_"
|
235
|
-
|
236
|
-
# Find the next number
|
237
|
-
next_number = 1
|
238
|
-
if existing_hotkeys:
|
239
|
-
numbers = []
|
240
|
-
for k in existing_hotkeys:
|
241
|
-
# Extract number from name if it follows our pattern
|
242
|
-
if (
|
243
|
-
k["name"].startswith(hotkey_prefix)
|
244
|
-
and k["name"][len(hotkey_prefix) :].isdigit()
|
245
|
-
):
|
246
|
-
numbers.append(int(k["name"][len(hotkey_prefix) :]))
|
247
|
-
|
248
|
-
if numbers:
|
249
|
-
next_number = max(numbers) + 1
|
250
|
-
|
251
|
-
name = f"{hotkey_prefix}{next_number}"
|
252
|
-
|
253
|
-
# Create keypair
|
254
|
-
keypair = Keypair.create_from_uri(f"//{name}_{uuid.uuid4()}")
|
255
|
-
|
256
|
-
# Save hotkey info
|
257
|
-
hotkey_info = {
|
258
|
-
"name": name,
|
259
|
-
"address": keypair.ss58_address,
|
260
|
-
"created_at": time.time(),
|
261
|
-
"public_key": keypair.public_key.hex(),
|
262
|
-
"mnemonic": keypair.mnemonic if hasattr(keypair, "mnemonic") else None,
|
263
|
-
"coldkey": coldkey_address, # Store the associated coldkey for clarity
|
264
|
-
}
|
265
|
-
|
266
|
-
data["hotkeys"][keypair.ss58_address] = hotkey_info
|
267
|
-
|
268
|
-
# Create relationship with coldkey
|
269
|
-
relationship = {
|
270
|
-
"coldkey": coldkey_address,
|
271
|
-
"hotkey": keypair.ss58_address,
|
272
|
-
"created_at": time.time(),
|
273
|
-
}
|
274
|
-
data["relationships"].append(relationship)
|
275
|
-
|
276
|
-
# Save updated data
|
277
|
-
self._save_accounts_data(data)
|
278
|
-
|
279
|
-
# Display information to the user
|
280
|
-
print(f"Hotkey created successfully!")
|
281
|
-
print(f"Name: {name}")
|
282
|
-
print(f"Address: {keypair.ss58_address}")
|
283
|
-
print(
|
284
|
-
f"Associated with coldkey: {coldkey_address} ({data['coldkeys'][coldkey_address]['name']})"
|
285
|
-
)
|
286
|
-
print(
|
287
|
-
f"Note: This is only a local association. To create a blockchain proxy relationship,"
|
288
|
-
)
|
289
|
-
print(
|
290
|
-
f"use: hippius account proxy create --coldkey {coldkey_address} --hotkey {keypair.ss58_address}"
|
291
|
-
)
|
292
|
-
|
293
|
-
return hotkey_info
|
294
|
-
|
295
|
-
def list_coldkeys(self) -> List[Dict]:
|
296
|
-
"""List all stored coldkeys."""
|
297
|
-
data = self._load_accounts_data()
|
298
|
-
return list(data["coldkeys"].values())
|
299
|
-
|
300
|
-
def list_hotkeys(self, coldkey_address: Optional[str] = None) -> List[Dict]:
|
301
|
-
"""
|
302
|
-
List all stored hotkeys, optionally filtered by associated coldkey.
|
303
|
-
|
304
|
-
Args:
|
305
|
-
coldkey_address: If provided, only return hotkeys associated with this coldkey
|
306
|
-
|
307
|
-
Returns:
|
308
|
-
List of hotkey information dictionaries with coldkey association information
|
309
|
-
"""
|
310
|
-
data = self._load_accounts_data()
|
311
|
-
|
312
|
-
# Build a mapping of hotkey address to associated coldkey
|
313
|
-
hotkey_to_coldkey = {}
|
314
|
-
for relationship in data.get("relationships", []):
|
315
|
-
hotkey = relationship.get("hotkey")
|
316
|
-
coldkey = relationship.get("coldkey")
|
317
|
-
if hotkey and coldkey:
|
318
|
-
hotkey_to_coldkey[hotkey] = coldkey
|
319
|
-
|
320
|
-
# Filter hotkeys based on coldkey_address if provided
|
321
|
-
if coldkey_address:
|
322
|
-
# Verify the coldkey exists
|
323
|
-
if coldkey_address not in data["coldkeys"]:
|
324
|
-
raise ValueError(f"Coldkey {coldkey_address} not found")
|
325
|
-
|
326
|
-
# Get all hotkeys associated with this coldkey
|
327
|
-
related_hotkey_addresses = [
|
328
|
-
hotkey
|
329
|
-
for hotkey, coldkey in hotkey_to_coldkey.items()
|
330
|
-
if coldkey == coldkey_address
|
331
|
-
]
|
332
|
-
|
333
|
-
# Return the hotkey information with added coldkey association
|
334
|
-
result = []
|
335
|
-
for addr in related_hotkey_addresses:
|
336
|
-
if addr in data["hotkeys"]:
|
337
|
-
hotkey_info = data["hotkeys"][addr].copy()
|
338
|
-
# Make sure the coldkey association is included in the info
|
339
|
-
hotkey_info["associated_coldkey"] = coldkey_address
|
340
|
-
hotkey_info["coldkey_name"] = data["coldkeys"][coldkey_address][
|
341
|
-
"name"
|
342
|
-
]
|
343
|
-
result.append(hotkey_info)
|
344
|
-
|
345
|
-
return result
|
346
|
-
else:
|
347
|
-
# Return all hotkeys with their coldkey associations
|
348
|
-
result = []
|
349
|
-
for addr, info in data["hotkeys"].items():
|
350
|
-
hotkey_info = info.copy()
|
351
|
-
# Add coldkey association information if available
|
352
|
-
if addr in hotkey_to_coldkey:
|
353
|
-
coldkey = hotkey_to_coldkey[addr]
|
354
|
-
hotkey_info["associated_coldkey"] = coldkey
|
355
|
-
if coldkey in data["coldkeys"]:
|
356
|
-
hotkey_info["coldkey_name"] = data["coldkeys"][coldkey]["name"]
|
357
|
-
result.append(hotkey_info)
|
358
|
-
|
359
|
-
return result
|
360
|
-
|
361
|
-
def create_proxy_relationship(
|
362
|
-
self,
|
363
|
-
coldkey_address: str,
|
364
|
-
hotkey_address: str,
|
365
|
-
proxy_type: str = "NonTransfer",
|
366
|
-
delay: int = 0,
|
367
|
-
password: Optional[str] = None,
|
368
|
-
) -> Dict:
|
369
|
-
"""
|
370
|
-
Create a proxy relationship between coldkey and hotkey on the blockchain.
|
371
|
-
|
372
|
-
Args:
|
373
|
-
coldkey_address: The SS58 address of the coldkey (delegator)
|
374
|
-
hotkey_address: The SS58 address of the hotkey (delegate)
|
375
|
-
proxy_type: The proxy type (default: "NonTransfer")
|
376
|
-
delay: Delay in blocks before the proxy becomes active
|
377
|
-
password: Optional password for decrypting the coldkey mnemonic
|
378
|
-
|
379
|
-
Returns:
|
380
|
-
Dict with transaction details
|
381
|
-
"""
|
382
|
-
# Verify the accounts exist
|
383
|
-
data = self._load_accounts_data()
|
384
|
-
if coldkey_address not in data["coldkeys"]:
|
385
|
-
raise ValueError(f"Coldkey {coldkey_address} not found")
|
386
|
-
|
387
|
-
if hotkey_address not in data["hotkeys"]:
|
388
|
-
raise ValueError(f"Hotkey {hotkey_address} not found")
|
389
|
-
|
390
|
-
# Get the mnemonic for the coldkey and create a keypair
|
391
|
-
try:
|
392
|
-
# Try first with our own method which handles our encrypted storage
|
393
|
-
mnemonic = self.get_coldkey_mnemonic(coldkey_address, password)
|
394
|
-
keypair = Keypair.create_from_mnemonic(mnemonic)
|
395
|
-
except Exception as e:
|
396
|
-
print(f"Could not retrieve coldkey mnemonic: {str(e)}")
|
397
|
-
print("Falling back to global config keypair...")
|
398
|
-
|
399
|
-
# Fall back to the config system's get_keypair as a backup
|
400
|
-
from hippius_sdk.config import get_keypair
|
401
|
-
|
402
|
-
keypair = get_keypair(ss58_address=coldkey_address)
|
403
|
-
|
404
|
-
# Create proxy
|
405
|
-
call = self.substrate.compose_call(
|
406
|
-
call_module="Proxy",
|
407
|
-
call_function="addProxy",
|
408
|
-
call_params={
|
409
|
-
"delegate": hotkey_address,
|
410
|
-
"proxyType": proxy_type,
|
411
|
-
"delay": delay,
|
412
|
-
},
|
413
|
-
)
|
414
|
-
|
415
|
-
# Create and sign extrinsic
|
416
|
-
extrinsic = self.substrate.create_signed_extrinsic(call=call, keypair=keypair)
|
417
|
-
|
418
|
-
# Submit and get result
|
419
|
-
try:
|
420
|
-
response = self.substrate.submit_extrinsic(
|
421
|
-
extrinsic=extrinsic, wait_for_inclusion=True
|
422
|
-
)
|
423
|
-
|
424
|
-
result = {
|
425
|
-
"success": True,
|
426
|
-
"transaction_hash": response.extrinsic_hash,
|
427
|
-
"block_hash": response.block_hash,
|
428
|
-
"coldkey": coldkey_address,
|
429
|
-
"hotkey": hotkey_address,
|
430
|
-
"proxy_type": proxy_type,
|
431
|
-
"delay": delay,
|
432
|
-
}
|
433
|
-
|
434
|
-
# Update local records
|
435
|
-
relationship = {
|
436
|
-
"coldkey": coldkey_address,
|
437
|
-
"hotkey": hotkey_address,
|
438
|
-
"proxy_type": proxy_type,
|
439
|
-
"delay": delay,
|
440
|
-
"created_at": time.time(),
|
441
|
-
"transaction_hash": response.extrinsic_hash,
|
442
|
-
}
|
443
|
-
data["relationships"].append(relationship)
|
444
|
-
self._save_accounts_data(data)
|
445
|
-
|
446
|
-
return result
|
447
|
-
|
448
|
-
except SubstrateRequestException as e:
|
449
|
-
return {
|
450
|
-
"success": False,
|
451
|
-
"error": str(e),
|
452
|
-
"coldkey": coldkey_address,
|
453
|
-
"hotkey": hotkey_address,
|
454
|
-
}
|
455
|
-
|
456
|
-
def list_proxies(self, coldkey_address: Optional[str] = None) -> List[Dict]:
|
457
|
-
"""
|
458
|
-
List proxy relationships from the blockchain, optionally filtered by coldkey.
|
459
|
-
|
460
|
-
Args:
|
461
|
-
coldkey_address: If provided, only return proxies for this coldkey
|
462
|
-
|
463
|
-
Returns:
|
464
|
-
List of proxy relationship information dictionaries
|
465
|
-
"""
|
466
|
-
# Query the chain for registered proxies
|
467
|
-
chain_proxies = []
|
468
|
-
|
469
|
-
# If coldkey specified, only query for that address
|
470
|
-
addresses_to_query = [coldkey_address] if coldkey_address else []
|
471
|
-
|
472
|
-
# If no specific coldkey, get all coldkeys from our records
|
473
|
-
if not coldkey_address:
|
474
|
-
data = self._load_accounts_data()
|
475
|
-
addresses_to_query = list(data["coldkeys"].keys())
|
476
|
-
|
477
|
-
# Query chain for each address
|
478
|
-
for address in addresses_to_query:
|
479
|
-
try:
|
480
|
-
result = self.substrate.query(
|
481
|
-
module="Proxy", storage_function="Proxies", params=[address]
|
482
|
-
)
|
483
|
-
|
484
|
-
# Format depends on the specific chain implementation
|
485
|
-
if result and result.value:
|
486
|
-
proxies_data = result.value[0] # Typically [proxies, deposit]
|
487
|
-
|
488
|
-
for proxy in proxies_data:
|
489
|
-
chain_proxies.append(
|
490
|
-
{
|
491
|
-
"coldkey": address,
|
492
|
-
"hotkey": proxy["delegate"],
|
493
|
-
"proxy_type": proxy["proxyType"],
|
494
|
-
"delay": proxy["delay"],
|
495
|
-
"source": "blockchain",
|
496
|
-
}
|
497
|
-
)
|
498
|
-
except Exception as e:
|
499
|
-
print(f"Error querying proxies for {address}: {str(e)}")
|
500
|
-
|
501
|
-
return chain_proxies
|
502
|
-
|
503
|
-
def remove_proxy(
|
504
|
-
self,
|
505
|
-
coldkey_address: str,
|
506
|
-
hotkey_address: str,
|
507
|
-
password: Optional[str] = None,
|
508
|
-
proxy_type: str = "NonTransfer",
|
509
|
-
delay: int = 0,
|
510
|
-
) -> Dict:
|
511
|
-
"""
|
512
|
-
Remove a proxy relationship from the blockchain.
|
513
|
-
|
514
|
-
Args:
|
515
|
-
coldkey_address: The SS58 address of the coldkey (delegator)
|
516
|
-
hotkey_address: The SS58 address of the hotkey (delegate)
|
517
|
-
password: Optional password for decrypting the coldkey mnemonic
|
518
|
-
proxy_type: The proxy type (default: "NonTransfer")
|
519
|
-
delay: Delay value used when creating the proxy
|
520
|
-
|
521
|
-
Returns:
|
522
|
-
Dict with transaction details
|
523
|
-
"""
|
524
|
-
# Get the mnemonic for the coldkey and create a keypair
|
525
|
-
try:
|
526
|
-
# Try first with our own method which handles our encrypted storage
|
527
|
-
mnemonic = self.get_coldkey_mnemonic(coldkey_address, password)
|
528
|
-
keypair = Keypair.create_from_mnemonic(mnemonic)
|
529
|
-
except Exception as e:
|
530
|
-
print(f"Could not retrieve coldkey mnemonic: {str(e)}")
|
531
|
-
print("Falling back to global config keypair...")
|
532
|
-
|
533
|
-
# Fall back to the config system's get_keypair as a backup
|
534
|
-
from hippius_sdk.config import get_keypair
|
535
|
-
|
536
|
-
keypair = get_keypair(ss58_address=coldkey_address)
|
537
|
-
|
538
|
-
# Remove proxy
|
539
|
-
call = self.substrate.compose_call(
|
540
|
-
call_module="Proxy",
|
541
|
-
call_function="removeProxy",
|
542
|
-
call_params={
|
543
|
-
"delegate": hotkey_address,
|
544
|
-
"proxyType": proxy_type,
|
545
|
-
"delay": delay,
|
546
|
-
},
|
547
|
-
)
|
548
|
-
|
549
|
-
# Create and sign extrinsic
|
550
|
-
extrinsic = self.substrate.create_signed_extrinsic(call=call, keypair=keypair)
|
551
|
-
|
552
|
-
# Submit and get result
|
553
|
-
try:
|
554
|
-
response = self.substrate.submit_extrinsic(
|
555
|
-
extrinsic=extrinsic, wait_for_inclusion=True
|
556
|
-
)
|
557
|
-
|
558
|
-
# Update local records
|
559
|
-
data = self._load_accounts_data()
|
560
|
-
data["relationships"] = [
|
561
|
-
r
|
562
|
-
for r in data["relationships"]
|
563
|
-
if not (
|
564
|
-
r["coldkey"] == coldkey_address and r["hotkey"] == hotkey_address
|
565
|
-
)
|
566
|
-
]
|
567
|
-
self._save_accounts_data(data)
|
568
|
-
|
569
|
-
return {
|
570
|
-
"success": True,
|
571
|
-
"transaction_hash": response.extrinsic_hash,
|
572
|
-
"block_hash": response.block_hash,
|
573
|
-
"coldkey": coldkey_address,
|
574
|
-
"hotkey": hotkey_address,
|
575
|
-
}
|
576
|
-
|
577
|
-
except SubstrateRequestException as e:
|
578
|
-
return {
|
579
|
-
"success": False,
|
580
|
-
"error": str(e),
|
581
|
-
"coldkey": coldkey_address,
|
582
|
-
"hotkey": hotkey_address,
|
583
|
-
}
|
584
|
-
|
585
|
-
def get_coldkey_mnemonic(
|
586
|
-
self, coldkey_address: str, password: Optional[str] = None
|
587
|
-
) -> str:
|
588
|
-
"""
|
589
|
-
Get a coldkey's mnemonic, decrypting it if necessary.
|
590
|
-
|
591
|
-
Args:
|
592
|
-
coldkey_address: The SS58 address of the coldkey
|
593
|
-
password: Optional password for decryption (if None and needed, will prompt)
|
594
|
-
|
595
|
-
Returns:
|
596
|
-
str: The mnemonic seed phrase
|
597
|
-
|
598
|
-
Raises:
|
599
|
-
ValueError: If the coldkey doesn't exist or the password is incorrect
|
600
|
-
"""
|
601
|
-
# Load the coldkey data
|
602
|
-
data = self._load_accounts_data()
|
603
|
-
|
604
|
-
if coldkey_address not in data["coldkeys"]:
|
605
|
-
raise ValueError(f"Coldkey {coldkey_address} not found")
|
606
|
-
|
607
|
-
coldkey_info = data["coldkeys"][coldkey_address]
|
608
|
-
|
609
|
-
# Check if the mnemonic is encrypted
|
610
|
-
if coldkey_info.get("encrypted", False):
|
611
|
-
# If encrypted, we need to decrypt it
|
612
|
-
if "mnemonic" not in coldkey_info or "salt" not in coldkey_info:
|
613
|
-
# Try to get from main config as fallback
|
614
|
-
from hippius_sdk.config import get_seed_phrase
|
615
|
-
|
616
|
-
account_name = f"{coldkey_info['name']}_{coldkey_address}"
|
617
|
-
|
618
|
-
mnemonic = get_seed_phrase(password=password, account_name=account_name)
|
619
|
-
if mnemonic:
|
620
|
-
return mnemonic
|
621
|
-
|
622
|
-
raise ValueError(
|
623
|
-
f"Mnemonic for coldkey {coldkey_address} is marked as encrypted "
|
624
|
-
f"but encryption data is missing"
|
625
|
-
)
|
626
|
-
|
627
|
-
# Import needed modules for decryption
|
628
|
-
import getpass
|
629
|
-
from hippius_sdk.config import decrypt_with_password
|
630
|
-
|
631
|
-
# Get password if not provided
|
632
|
-
if password is None:
|
633
|
-
password = getpass.getpass("Enter password to decrypt mnemonic: ")
|
634
|
-
|
635
|
-
# Decrypt the mnemonic
|
636
|
-
try:
|
637
|
-
encrypted_data = coldkey_info["mnemonic"]
|
638
|
-
salt = coldkey_info["salt"]
|
639
|
-
mnemonic = decrypt_with_password(encrypted_data, salt, password)
|
640
|
-
return mnemonic
|
641
|
-
except Exception as e:
|
642
|
-
raise ValueError(f"Failed to decrypt mnemonic: {str(e)}")
|
643
|
-
else:
|
644
|
-
# If not encrypted, just return the mnemonic
|
645
|
-
if "mnemonic" not in coldkey_info:
|
646
|
-
raise ValueError(f"No mnemonic found for coldkey {coldkey_address}")
|
647
|
-
|
648
|
-
return coldkey_info["mnemonic"]
|