htcli 1.1.0__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.
- htcli-1.1.0.dist-info/METADATA +509 -0
- htcli-1.1.0.dist-info/RECORD +140 -0
- htcli-1.1.0.dist-info/WHEEL +4 -0
- htcli-1.1.0.dist-info/entry_points.txt +2 -0
- htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
- src/__init__.py +0 -0
- src/htcli/__init__.py +5 -0
- src/htcli/client/__init__.py +338 -0
- src/htcli/client/extrinsics/__init__.py +26 -0
- src/htcli/client/extrinsics/base.py +487 -0
- src/htcli/client/extrinsics/consensus.py +79 -0
- src/htcli/client/extrinsics/governance.py +714 -0
- src/htcli/client/extrinsics/identity.py +490 -0
- src/htcli/client/extrinsics/node.py +1054 -0
- src/htcli/client/extrinsics/overwatch.py +401 -0
- src/htcli/client/extrinsics/staking.py +1504 -0
- src/htcli/client/extrinsics/subnet.py +2218 -0
- src/htcli/client/extrinsics/validator.py +203 -0
- src/htcli/client/extrinsics/wallet.py +323 -0
- src/htcli/client/offchain/__init__.py +10 -0
- src/htcli/client/offchain/backup.py +385 -0
- src/htcli/client/offchain/config.py +541 -0
- src/htcli/client/offchain/wallet.py +839 -0
- src/htcli/client/rpc/__init__.py +20 -0
- src/htcli/client/rpc/chain.py +568 -0
- src/htcli/client/rpc/node.py +783 -0
- src/htcli/client/rpc/overwatch.py +680 -0
- src/htcli/client/rpc/staking.py +216 -0
- src/htcli/client/rpc/subnet.py +2104 -0
- src/htcli/client/rpc/wallet.py +912 -0
- src/htcli/commands/__init__.py +31 -0
- src/htcli/commands/chain/__init__.py +66 -0
- src/htcli/commands/chain/display.py +204 -0
- src/htcli/commands/chain/handlers.py +260 -0
- src/htcli/commands/config/__init__.py +158 -0
- src/htcli/commands/config/display.py +353 -0
- src/htcli/commands/config/handlers.py +347 -0
- src/htcli/commands/config/prompts.py +357 -0
- src/htcli/commands/consensus/__init__.py +61 -0
- src/htcli/commands/consensus/handlers.py +100 -0
- src/htcli/commands/governance/__init__.py +49 -0
- src/htcli/commands/governance/handlers.py +81 -0
- src/htcli/commands/node/__init__.py +304 -0
- src/htcli/commands/node/display.py +749 -0
- src/htcli/commands/node/error_handling.py +470 -0
- src/htcli/commands/node/handlers.py +844 -0
- src/htcli/commands/node/prompts.py +346 -0
- src/htcli/commands/overwatch/__init__.py +219 -0
- src/htcli/commands/overwatch/display.py +396 -0
- src/htcli/commands/overwatch/error_handling.py +276 -0
- src/htcli/commands/overwatch/handlers.py +443 -0
- src/htcli/commands/overwatch/prompts.py +359 -0
- src/htcli/commands/stake/__init__.py +736 -0
- src/htcli/commands/stake/display.py +1103 -0
- src/htcli/commands/stake/error_handling.py +425 -0
- src/htcli/commands/stake/handlers.py +1902 -0
- src/htcli/commands/stake/prompts.py +1080 -0
- src/htcli/commands/subnet/__init__.py +639 -0
- src/htcli/commands/subnet/display.py +801 -0
- src/htcli/commands/subnet/error_handling.py +524 -0
- src/htcli/commands/subnet/handlers.py +2855 -0
- src/htcli/commands/subnet/prompts.py +1225 -0
- src/htcli/commands/validator/__init__.py +192 -0
- src/htcli/commands/validator/display.py +54 -0
- src/htcli/commands/validator/handlers.py +340 -0
- src/htcli/commands/wallet/__init__.py +546 -0
- src/htcli/commands/wallet/display.py +806 -0
- src/htcli/commands/wallet/error_handling.py +210 -0
- src/htcli/commands/wallet/handlers.py +3040 -0
- src/htcli/commands/wallet/prompts.py +1518 -0
- src/htcli/config.py +184 -0
- src/htcli/dependencies.py +186 -0
- src/htcli/errors/__init__.py +63 -0
- src/htcli/errors/base.py +141 -0
- src/htcli/errors/display.py +20 -0
- src/htcli/errors/handlers.py +710 -0
- src/htcli/main.py +343 -0
- src/htcli/models/__init__.py +21 -0
- src/htcli/models/enums/enum_types.py +35 -0
- src/htcli/models/errors.py +103 -0
- src/htcli/models/requests/__init__.py +197 -0
- src/htcli/models/requests/config.py +70 -0
- src/htcli/models/requests/consensus.py +19 -0
- src/htcli/models/requests/governance.py +38 -0
- src/htcli/models/requests/identity.py +51 -0
- src/htcli/models/requests/key.py +22 -0
- src/htcli/models/requests/node.py +91 -0
- src/htcli/models/requests/overwatch.py +64 -0
- src/htcli/models/requests/staking.py +580 -0
- src/htcli/models/requests/subnet.py +195 -0
- src/htcli/models/requests/validator.py +139 -0
- src/htcli/models/requests/wallet.py +118 -0
- src/htcli/models/responses/__init__.py +147 -0
- src/htcli/models/responses/base.py +18 -0
- src/htcli/models/responses/chain.py +39 -0
- src/htcli/models/responses/config.py +58 -0
- src/htcli/models/responses/identity.py +102 -0
- src/htcli/models/responses/overwatch.py +51 -0
- src/htcli/models/responses/staking.py +502 -0
- src/htcli/models/responses/subnet.py +856 -0
- src/htcli/models/responses/wallet.py +185 -0
- src/htcli/ui/__init__.py +87 -0
- src/htcli/ui/colors.py +309 -0
- src/htcli/ui/components/__init__.py +60 -0
- src/htcli/ui/components/panels.py +174 -0
- src/htcli/ui/components/progress.py +166 -0
- src/htcli/ui/components/spinners.py +92 -0
- src/htcli/ui/components/tables.py +809 -0
- src/htcli/ui/components/trees.py +721 -0
- src/htcli/ui/display.py +336 -0
- src/htcli/ui/prompts.py +870 -0
- src/htcli/utils/__init__.py +76 -0
- src/htcli/utils/blockchain/__init__.py +75 -0
- src/htcli/utils/blockchain/formatting.py +368 -0
- src/htcli/utils/blockchain/patches.py +286 -0
- src/htcli/utils/blockchain/peer_id.py +186 -0
- src/htcli/utils/blockchain/staking.py +448 -0
- src/htcli/utils/blockchain/type_registry.py +1373 -0
- src/htcli/utils/blockchain/validation.py +179 -0
- src/htcli/utils/cache.py +613 -0
- src/htcli/utils/constants.py +38 -0
- src/htcli/utils/legacy/__init__.py +12 -0
- src/htcli/utils/legacy/colors.py +311 -0
- src/htcli/utils/legacy/crypto.py +1176 -0
- src/htcli/utils/legacy/formatting.py +452 -0
- src/htcli/utils/legacy/interactive.py +306 -0
- src/htcli/utils/legacy/subnet_manifest.py +265 -0
- src/htcli/utils/legacy/validation.py +488 -0
- src/htcli/utils/logging.py +183 -0
- src/htcli/utils/network/__init__.py +20 -0
- src/htcli/utils/network/subnet.py +344 -0
- src/htcli/utils/prompts.py +27 -0
- src/htcli/utils/scale_codec.py +155 -0
- src/htcli/utils/validation/__init__.py +57 -0
- src/htcli/utils/validation/prompt_validators.py +267 -0
- src/htcli/utils/wallet/__init__.py +65 -0
- src/htcli/utils/wallet/auth.py +151 -0
- src/htcli/utils/wallet/core.py +1069 -0
- src/htcli/utils/wallet/crypto.py +1615 -0
- src/htcli/utils/wallet/migration.py +159 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Identity management extrinsics for HTCLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from substrateinterface import Keypair, SubstrateInterface
|
|
8
|
+
|
|
9
|
+
from ...models.requests.identity import (
|
|
10
|
+
IdentityAcceptRequest,
|
|
11
|
+
IdentityRegisterRequest,
|
|
12
|
+
IdentityRemoveRequest,
|
|
13
|
+
IdentityUpdateColdkeyRequest,
|
|
14
|
+
IdentityUpdateHotkeyRequest,
|
|
15
|
+
)
|
|
16
|
+
from ...models.responses.identity import IdentityInfo
|
|
17
|
+
from ...utils.blockchain import validate_address
|
|
18
|
+
from ...utils.logging import get_logger
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class IdentityExtrinsics:
|
|
24
|
+
"""Client for identity-related extrinsics."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, substrate: Optional[SubstrateInterface] = None):
|
|
27
|
+
"""Initialize the identity client."""
|
|
28
|
+
self.substrate = substrate
|
|
29
|
+
|
|
30
|
+
# ============================================================================
|
|
31
|
+
# IDENTITY REGISTRATION
|
|
32
|
+
# ============================================================================
|
|
33
|
+
|
|
34
|
+
def register_or_update_identity(
|
|
35
|
+
self, request: IdentityRegisterRequest, keypair: Keypair
|
|
36
|
+
) -> dict[str, Any]:
|
|
37
|
+
"""
|
|
38
|
+
Register or update an identity.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
request: Identity registration request
|
|
42
|
+
keypair: Keypair for signing the transaction
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
dictionary with operation result
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
logger.info(f"Registering/updating identity for hotkey {request.hotkey}")
|
|
49
|
+
|
|
50
|
+
# Validate inputs
|
|
51
|
+
self._validate_identity_request(request)
|
|
52
|
+
|
|
53
|
+
# Prepare identity data
|
|
54
|
+
identity_data = {
|
|
55
|
+
"name": request.name.encode("utf-8"),
|
|
56
|
+
"url": request.url.encode("utf-8"),
|
|
57
|
+
"image": request.image.encode("utf-8"),
|
|
58
|
+
"discord": request.discord.encode("utf-8"),
|
|
59
|
+
"x": request.x.encode("utf-8"),
|
|
60
|
+
"telegram": request.telegram.encode("utf-8"),
|
|
61
|
+
"github": request.github.encode("utf-8"),
|
|
62
|
+
"hugging_face": request.hugging_face.encode("utf-8"),
|
|
63
|
+
"description": request.description.encode("utf-8"),
|
|
64
|
+
"misc": request.misc.encode("utf-8"),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
call = self.substrate.compose_call(
|
|
68
|
+
call_module="Network",
|
|
69
|
+
call_function="register_or_update_identity",
|
|
70
|
+
call_params={
|
|
71
|
+
"hotkey": request.hotkey,
|
|
72
|
+
"name": identity_data["name"],
|
|
73
|
+
"url": identity_data["url"],
|
|
74
|
+
"image": identity_data["image"],
|
|
75
|
+
"discord": identity_data["discord"],
|
|
76
|
+
"x": identity_data["x"],
|
|
77
|
+
"telegram": identity_data["telegram"],
|
|
78
|
+
"github": identity_data["github"],
|
|
79
|
+
"hugging_face": identity_data["hugging_face"],
|
|
80
|
+
"description": identity_data["description"],
|
|
81
|
+
"misc": identity_data["misc"],
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
response = self._submit_extrinsic(call, keypair)
|
|
86
|
+
|
|
87
|
+
if response["success"]:
|
|
88
|
+
response["message"] = (
|
|
89
|
+
f"Identity registered/updated successfully for hotkey {request.hotkey}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return response
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(f"Error registering/updating identity: {e}")
|
|
96
|
+
return {"success": False, "error": str(e)}
|
|
97
|
+
|
|
98
|
+
# ============================================================================
|
|
99
|
+
# IDENTITY REMOVAL
|
|
100
|
+
# ============================================================================
|
|
101
|
+
|
|
102
|
+
def remove_identity(
|
|
103
|
+
self, request: IdentityRemoveRequest, keypair: Keypair
|
|
104
|
+
) -> dict[str, Any]:
|
|
105
|
+
"""
|
|
106
|
+
Remove an identity.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
request: Identity removal request
|
|
110
|
+
keypair: Keypair for signing the transaction
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
dictionary with operation result
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
logger.info("Removing identity")
|
|
117
|
+
|
|
118
|
+
call = self.substrate.compose_call(
|
|
119
|
+
call_module="Network", call_function="remove_identity", call_params={}
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
response = self._submit_extrinsic(call, keypair)
|
|
123
|
+
|
|
124
|
+
if response["success"]:
|
|
125
|
+
response["message"] = "Identity removed successfully"
|
|
126
|
+
|
|
127
|
+
return response
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"Error removing identity: {e}")
|
|
131
|
+
return {"success": False, "error": str(e)}
|
|
132
|
+
|
|
133
|
+
# ============================================================================
|
|
134
|
+
# IDENTITY ACCEPTANCE
|
|
135
|
+
# ============================================================================
|
|
136
|
+
|
|
137
|
+
def accept_identity(
|
|
138
|
+
self, request: IdentityAcceptRequest, keypair: Keypair
|
|
139
|
+
) -> dict[str, Any]:
|
|
140
|
+
"""
|
|
141
|
+
Accept an identity.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
request: Identity accept request
|
|
145
|
+
keypair: Keypair for signing the transaction
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
dictionary with operation result
|
|
149
|
+
"""
|
|
150
|
+
try:
|
|
151
|
+
logger.info("Accepting identity")
|
|
152
|
+
|
|
153
|
+
# Validate inputs
|
|
154
|
+
self._validate_identity_accept_request(request)
|
|
155
|
+
|
|
156
|
+
call = self.substrate.compose_call(
|
|
157
|
+
call_module="Network",
|
|
158
|
+
call_function="accept_identity",
|
|
159
|
+
call_params={"identity": request.identity.encode("utf-8")},
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
response = self._submit_extrinsic(call, keypair)
|
|
163
|
+
|
|
164
|
+
if response["success"]:
|
|
165
|
+
response["message"] = "Identity accepted successfully"
|
|
166
|
+
|
|
167
|
+
return response
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"Error accepting identity: {e}")
|
|
171
|
+
return {"success": False, "error": str(e)}
|
|
172
|
+
|
|
173
|
+
# ============================================================================
|
|
174
|
+
# KEY ROTATION
|
|
175
|
+
# ============================================================================
|
|
176
|
+
|
|
177
|
+
def update_coldkey(
|
|
178
|
+
self, request: IdentityUpdateColdkeyRequest, keypair: Keypair
|
|
179
|
+
) -> dict[str, Any]:
|
|
180
|
+
"""
|
|
181
|
+
Update coldkey.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
request: Coldkey update request
|
|
185
|
+
keypair: Keypair for signing the transaction (current coldkey)
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
dictionary with operation result
|
|
189
|
+
"""
|
|
190
|
+
try:
|
|
191
|
+
logger.info(f"Updating coldkey to {request.new_coldkey}")
|
|
192
|
+
|
|
193
|
+
# Validate input
|
|
194
|
+
validate_address(request.new_coldkey)
|
|
195
|
+
|
|
196
|
+
call = self.substrate.compose_call(
|
|
197
|
+
call_module="Network",
|
|
198
|
+
call_function="update_coldkey",
|
|
199
|
+
call_params={"new_coldkey": request.new_coldkey},
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
response = self._submit_extrinsic(call, keypair)
|
|
203
|
+
|
|
204
|
+
if response["success"]:
|
|
205
|
+
response["message"] = (
|
|
206
|
+
f"Coldkey updated successfully to {request.new_coldkey}"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return response
|
|
210
|
+
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.error(f"Error updating coldkey: {e}")
|
|
213
|
+
return {"success": False, "error": str(e)}
|
|
214
|
+
|
|
215
|
+
def update_hotkey(
|
|
216
|
+
self, request: IdentityUpdateHotkeyRequest, keypair: Keypair
|
|
217
|
+
) -> dict[str, Any]:
|
|
218
|
+
"""
|
|
219
|
+
Update hotkey.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
request: Hotkey update request
|
|
223
|
+
keypair: Keypair for signing the transaction (coldkey)
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
dictionary with operation result
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
logger.info(f"Updating hotkey to {request.new_hotkey}")
|
|
230
|
+
|
|
231
|
+
# Validate input
|
|
232
|
+
validate_address(request.new_hotkey)
|
|
233
|
+
|
|
234
|
+
call = self.substrate.compose_call(
|
|
235
|
+
call_module="Network",
|
|
236
|
+
call_function="update_hotkey",
|
|
237
|
+
call_params={"new_hotkey": request.new_hotkey},
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
response = self._submit_extrinsic(call, keypair)
|
|
241
|
+
|
|
242
|
+
if response["success"]:
|
|
243
|
+
response["message"] = (
|
|
244
|
+
f"Hotkey updated successfully to {request.new_hotkey}"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return response
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
logger.error(f"Error updating hotkey: {e}")
|
|
251
|
+
return {"success": False, "error": str(e)}
|
|
252
|
+
|
|
253
|
+
# ============================================================================
|
|
254
|
+
# QUERY METHODS
|
|
255
|
+
# ============================================================================
|
|
256
|
+
|
|
257
|
+
def get_identity_info(self, coldkey: str) -> Optional[IdentityInfo]:
|
|
258
|
+
"""
|
|
259
|
+
Get identity information for a coldkey.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
coldkey: The coldkey account ID
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
IdentityInfo if found, None otherwise
|
|
266
|
+
"""
|
|
267
|
+
try:
|
|
268
|
+
# Query identity data
|
|
269
|
+
identity_data = self.substrate.query(
|
|
270
|
+
module="Network", storage_function="Identity", params=[coldkey]
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if not identity_data.value:
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
# Query hotkey associated with this coldkey
|
|
277
|
+
hotkey = self.substrate.query(
|
|
278
|
+
module="Network", storage_function="ColdkeyToHotkey", params=[coldkey]
|
|
279
|
+
).value
|
|
280
|
+
|
|
281
|
+
return IdentityInfo(
|
|
282
|
+
coldkey=coldkey,
|
|
283
|
+
hotkey=hotkey,
|
|
284
|
+
name=identity_data.value["name"].decode("utf-8"),
|
|
285
|
+
url=identity_data.value["url"].decode("utf-8"),
|
|
286
|
+
image=identity_data.value["image"].decode("utf-8"),
|
|
287
|
+
description=identity_data.value["description"].decode("utf-8"),
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
logger.error(f"Error getting identity info: {e}")
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
def get_identity_by_hotkey(self, hotkey: str) -> Optional[IdentityInfo]:
|
|
295
|
+
"""
|
|
296
|
+
Get identity information by hotkey.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
hotkey: The hotkey account ID
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
IdentityInfo if found, None otherwise
|
|
303
|
+
"""
|
|
304
|
+
try:
|
|
305
|
+
# Query coldkey associated with this hotkey
|
|
306
|
+
coldkey = self.substrate.query(
|
|
307
|
+
module="Network", storage_function="HotkeyToColdkey", params=[hotkey]
|
|
308
|
+
).value
|
|
309
|
+
|
|
310
|
+
if not coldkey:
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
return self.get_identity_info(coldkey)
|
|
314
|
+
|
|
315
|
+
except Exception as e:
|
|
316
|
+
logger.error(f"Error getting identity by hotkey: {e}")
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
def list_identities(self, limit: int = 100) -> list[IdentityInfo]:
|
|
320
|
+
"""
|
|
321
|
+
list all registered identities.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
limit: Maximum number of identities to return
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
list of IdentityInfo objects
|
|
328
|
+
"""
|
|
329
|
+
try:
|
|
330
|
+
identities = []
|
|
331
|
+
|
|
332
|
+
# Query all identity entries
|
|
333
|
+
# Note: This is a simplified approach. In practice, you might need
|
|
334
|
+
# to iterate through all possible account IDs or use a different method
|
|
335
|
+
# depending on how identities are stored in the chain
|
|
336
|
+
|
|
337
|
+
# For now, we'll return an empty list as this would require
|
|
338
|
+
# knowing the storage structure better
|
|
339
|
+
logger.warning(
|
|
340
|
+
"list identities not fully implemented - requires knowledge of storage iteration"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
return identities
|
|
344
|
+
|
|
345
|
+
except Exception as e:
|
|
346
|
+
logger.error(f"Error listing identities: {e}")
|
|
347
|
+
return []
|
|
348
|
+
|
|
349
|
+
def search_identities_by_name(self, name_query: str) -> list[IdentityInfo]:
|
|
350
|
+
"""
|
|
351
|
+
Search identities by name (partial match).
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
name_query: Name to search for
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
list of matching IdentityInfo objects
|
|
358
|
+
"""
|
|
359
|
+
try:
|
|
360
|
+
# This would require iterating through all identities and checking names
|
|
361
|
+
# For now, return empty list as this is not efficiently implementable
|
|
362
|
+
# without knowing the storage structure
|
|
363
|
+
logger.warning(
|
|
364
|
+
"Search identities by name not fully implemented - requires storage iteration"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return []
|
|
368
|
+
|
|
369
|
+
except Exception as e:
|
|
370
|
+
logger.error(f"Error searching identities by name: {e}")
|
|
371
|
+
return []
|
|
372
|
+
|
|
373
|
+
def get_identity_count(self) -> int:
|
|
374
|
+
"""
|
|
375
|
+
Get the total number of registered identities.
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Total number of identities
|
|
379
|
+
"""
|
|
380
|
+
try:
|
|
381
|
+
# Query total identity count
|
|
382
|
+
count = (
|
|
383
|
+
self.substrate.query(
|
|
384
|
+
module="Network", storage_function="TotalIdentities"
|
|
385
|
+
).value
|
|
386
|
+
or 0
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
return count
|
|
390
|
+
|
|
391
|
+
except Exception as e:
|
|
392
|
+
logger.error(f"Error getting identity count: {e}")
|
|
393
|
+
return 0
|
|
394
|
+
|
|
395
|
+
# ============================================================================
|
|
396
|
+
# PRIVATE HELPER METHODS
|
|
397
|
+
# ============================================================================
|
|
398
|
+
|
|
399
|
+
def _validate_identity_request(self, request: IdentityRegisterRequest):
|
|
400
|
+
"""Validate the identity registration request."""
|
|
401
|
+
validate_address(request.hotkey)
|
|
402
|
+
|
|
403
|
+
# Validate name is not empty
|
|
404
|
+
if not request.name or not request.name.strip():
|
|
405
|
+
raise ValueError("Identity name cannot be empty")
|
|
406
|
+
|
|
407
|
+
# Validate URL format if provided
|
|
408
|
+
if request.url and request.url.strip():
|
|
409
|
+
if not self._is_valid_url(request.url):
|
|
410
|
+
raise ValueError("Invalid URL format")
|
|
411
|
+
|
|
412
|
+
# Validate image URL format if provided
|
|
413
|
+
if request.image and request.image.strip():
|
|
414
|
+
if not self._is_valid_url(request.image):
|
|
415
|
+
raise ValueError("Invalid image URL format")
|
|
416
|
+
|
|
417
|
+
# Validate GitHub URL format if provided
|
|
418
|
+
if request.github and request.github.strip():
|
|
419
|
+
if not self._is_valid_url(request.github):
|
|
420
|
+
raise ValueError("Invalid GitHub URL format")
|
|
421
|
+
|
|
422
|
+
# Validate Hugging Face URL format if provided
|
|
423
|
+
if request.hugging_face and request.hugging_face.strip():
|
|
424
|
+
if not self._is_valid_url(request.hugging_face):
|
|
425
|
+
raise ValueError("Invalid Hugging Face URL format")
|
|
426
|
+
|
|
427
|
+
def _validate_identity_accept_request(self, request: IdentityAcceptRequest):
|
|
428
|
+
"""Validate the identity accept request."""
|
|
429
|
+
if not request.identity or not request.identity.strip():
|
|
430
|
+
raise ValueError("Identity data cannot be empty")
|
|
431
|
+
|
|
432
|
+
def _is_valid_url(self, url: str) -> bool:
|
|
433
|
+
"""Basic URL validation."""
|
|
434
|
+
import re
|
|
435
|
+
|
|
436
|
+
url_pattern = re.compile(
|
|
437
|
+
r"^https?://" # http:// or https://
|
|
438
|
+
r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|" # domain...
|
|
439
|
+
r"localhost|" # localhost...
|
|
440
|
+
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # ...or ip
|
|
441
|
+
r"(?::\d+)?" # optional port
|
|
442
|
+
r"(?:/?|[/?]\S+)$",
|
|
443
|
+
re.IGNORECASE,
|
|
444
|
+
)
|
|
445
|
+
return url_pattern.match(url) is not None
|
|
446
|
+
|
|
447
|
+
def _submit_extrinsic(self, call, keypair: Keypair) -> dict[str, Any]:
|
|
448
|
+
"""Submit an extrinsic and return the result."""
|
|
449
|
+
try:
|
|
450
|
+
extrinsic = self.substrate.create_signed_extrinsic(
|
|
451
|
+
call=call,
|
|
452
|
+
keypair=keypair,
|
|
453
|
+
era={"period": 64}, # 64 block era
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
response = self.substrate.submit_extrinsic(
|
|
457
|
+
extrinsic, wait_for_inclusion=True, wait_for_finalization=True
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
if response.is_success:
|
|
461
|
+
return {
|
|
462
|
+
"success": True,
|
|
463
|
+
"extrinsic_hash": response.extrinsic_hash,
|
|
464
|
+
"block_hash": response.block_hash,
|
|
465
|
+
"block_number": response.block_number,
|
|
466
|
+
}
|
|
467
|
+
else:
|
|
468
|
+
error_msg = self._extract_error_message(response)
|
|
469
|
+
return {"success": False, "error": error_msg}
|
|
470
|
+
|
|
471
|
+
except Exception as e:
|
|
472
|
+
logger.error(f"Error submitting extrinsic: {e}")
|
|
473
|
+
return {"success": False, "error": str(e)}
|
|
474
|
+
|
|
475
|
+
def _extract_error_message(self, response) -> str:
|
|
476
|
+
"""Extract error message from failed response."""
|
|
477
|
+
try:
|
|
478
|
+
for event in response.triggered_events:
|
|
479
|
+
if (
|
|
480
|
+
event.value["module_id"] == "System"
|
|
481
|
+
and event.value["event_id"] == "ExtrinsicFailed"
|
|
482
|
+
):
|
|
483
|
+
error = event.value["attributes"][0]
|
|
484
|
+
if hasattr(error, "name"):
|
|
485
|
+
return f"Extrinsic failed: {error.name}"
|
|
486
|
+
return f"Extrinsic failed: {str(error)}"
|
|
487
|
+
except Exception as e:
|
|
488
|
+
logger.warning(f"Could not extract error message: {e}")
|
|
489
|
+
|
|
490
|
+
return "Unknown error occurred"
|