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.
Files changed (140) hide show
  1. htcli-1.1.0.dist-info/METADATA +509 -0
  2. htcli-1.1.0.dist-info/RECORD +140 -0
  3. htcli-1.1.0.dist-info/WHEEL +4 -0
  4. htcli-1.1.0.dist-info/entry_points.txt +2 -0
  5. htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
  6. src/__init__.py +0 -0
  7. src/htcli/__init__.py +5 -0
  8. src/htcli/client/__init__.py +338 -0
  9. src/htcli/client/extrinsics/__init__.py +26 -0
  10. src/htcli/client/extrinsics/base.py +487 -0
  11. src/htcli/client/extrinsics/consensus.py +79 -0
  12. src/htcli/client/extrinsics/governance.py +714 -0
  13. src/htcli/client/extrinsics/identity.py +490 -0
  14. src/htcli/client/extrinsics/node.py +1054 -0
  15. src/htcli/client/extrinsics/overwatch.py +401 -0
  16. src/htcli/client/extrinsics/staking.py +1504 -0
  17. src/htcli/client/extrinsics/subnet.py +2218 -0
  18. src/htcli/client/extrinsics/validator.py +203 -0
  19. src/htcli/client/extrinsics/wallet.py +323 -0
  20. src/htcli/client/offchain/__init__.py +10 -0
  21. src/htcli/client/offchain/backup.py +385 -0
  22. src/htcli/client/offchain/config.py +541 -0
  23. src/htcli/client/offchain/wallet.py +839 -0
  24. src/htcli/client/rpc/__init__.py +20 -0
  25. src/htcli/client/rpc/chain.py +568 -0
  26. src/htcli/client/rpc/node.py +783 -0
  27. src/htcli/client/rpc/overwatch.py +680 -0
  28. src/htcli/client/rpc/staking.py +216 -0
  29. src/htcli/client/rpc/subnet.py +2104 -0
  30. src/htcli/client/rpc/wallet.py +912 -0
  31. src/htcli/commands/__init__.py +31 -0
  32. src/htcli/commands/chain/__init__.py +66 -0
  33. src/htcli/commands/chain/display.py +204 -0
  34. src/htcli/commands/chain/handlers.py +260 -0
  35. src/htcli/commands/config/__init__.py +158 -0
  36. src/htcli/commands/config/display.py +353 -0
  37. src/htcli/commands/config/handlers.py +347 -0
  38. src/htcli/commands/config/prompts.py +357 -0
  39. src/htcli/commands/consensus/__init__.py +61 -0
  40. src/htcli/commands/consensus/handlers.py +100 -0
  41. src/htcli/commands/governance/__init__.py +49 -0
  42. src/htcli/commands/governance/handlers.py +81 -0
  43. src/htcli/commands/node/__init__.py +304 -0
  44. src/htcli/commands/node/display.py +749 -0
  45. src/htcli/commands/node/error_handling.py +470 -0
  46. src/htcli/commands/node/handlers.py +844 -0
  47. src/htcli/commands/node/prompts.py +346 -0
  48. src/htcli/commands/overwatch/__init__.py +219 -0
  49. src/htcli/commands/overwatch/display.py +396 -0
  50. src/htcli/commands/overwatch/error_handling.py +276 -0
  51. src/htcli/commands/overwatch/handlers.py +443 -0
  52. src/htcli/commands/overwatch/prompts.py +359 -0
  53. src/htcli/commands/stake/__init__.py +736 -0
  54. src/htcli/commands/stake/display.py +1103 -0
  55. src/htcli/commands/stake/error_handling.py +425 -0
  56. src/htcli/commands/stake/handlers.py +1902 -0
  57. src/htcli/commands/stake/prompts.py +1080 -0
  58. src/htcli/commands/subnet/__init__.py +639 -0
  59. src/htcli/commands/subnet/display.py +801 -0
  60. src/htcli/commands/subnet/error_handling.py +524 -0
  61. src/htcli/commands/subnet/handlers.py +2855 -0
  62. src/htcli/commands/subnet/prompts.py +1225 -0
  63. src/htcli/commands/validator/__init__.py +192 -0
  64. src/htcli/commands/validator/display.py +54 -0
  65. src/htcli/commands/validator/handlers.py +340 -0
  66. src/htcli/commands/wallet/__init__.py +546 -0
  67. src/htcli/commands/wallet/display.py +806 -0
  68. src/htcli/commands/wallet/error_handling.py +210 -0
  69. src/htcli/commands/wallet/handlers.py +3040 -0
  70. src/htcli/commands/wallet/prompts.py +1518 -0
  71. src/htcli/config.py +184 -0
  72. src/htcli/dependencies.py +186 -0
  73. src/htcli/errors/__init__.py +63 -0
  74. src/htcli/errors/base.py +141 -0
  75. src/htcli/errors/display.py +20 -0
  76. src/htcli/errors/handlers.py +710 -0
  77. src/htcli/main.py +343 -0
  78. src/htcli/models/__init__.py +21 -0
  79. src/htcli/models/enums/enum_types.py +35 -0
  80. src/htcli/models/errors.py +103 -0
  81. src/htcli/models/requests/__init__.py +197 -0
  82. src/htcli/models/requests/config.py +70 -0
  83. src/htcli/models/requests/consensus.py +19 -0
  84. src/htcli/models/requests/governance.py +38 -0
  85. src/htcli/models/requests/identity.py +51 -0
  86. src/htcli/models/requests/key.py +22 -0
  87. src/htcli/models/requests/node.py +91 -0
  88. src/htcli/models/requests/overwatch.py +64 -0
  89. src/htcli/models/requests/staking.py +580 -0
  90. src/htcli/models/requests/subnet.py +195 -0
  91. src/htcli/models/requests/validator.py +139 -0
  92. src/htcli/models/requests/wallet.py +118 -0
  93. src/htcli/models/responses/__init__.py +147 -0
  94. src/htcli/models/responses/base.py +18 -0
  95. src/htcli/models/responses/chain.py +39 -0
  96. src/htcli/models/responses/config.py +58 -0
  97. src/htcli/models/responses/identity.py +102 -0
  98. src/htcli/models/responses/overwatch.py +51 -0
  99. src/htcli/models/responses/staking.py +502 -0
  100. src/htcli/models/responses/subnet.py +856 -0
  101. src/htcli/models/responses/wallet.py +185 -0
  102. src/htcli/ui/__init__.py +87 -0
  103. src/htcli/ui/colors.py +309 -0
  104. src/htcli/ui/components/__init__.py +60 -0
  105. src/htcli/ui/components/panels.py +174 -0
  106. src/htcli/ui/components/progress.py +166 -0
  107. src/htcli/ui/components/spinners.py +92 -0
  108. src/htcli/ui/components/tables.py +809 -0
  109. src/htcli/ui/components/trees.py +721 -0
  110. src/htcli/ui/display.py +336 -0
  111. src/htcli/ui/prompts.py +870 -0
  112. src/htcli/utils/__init__.py +76 -0
  113. src/htcli/utils/blockchain/__init__.py +75 -0
  114. src/htcli/utils/blockchain/formatting.py +368 -0
  115. src/htcli/utils/blockchain/patches.py +286 -0
  116. src/htcli/utils/blockchain/peer_id.py +186 -0
  117. src/htcli/utils/blockchain/staking.py +448 -0
  118. src/htcli/utils/blockchain/type_registry.py +1373 -0
  119. src/htcli/utils/blockchain/validation.py +179 -0
  120. src/htcli/utils/cache.py +613 -0
  121. src/htcli/utils/constants.py +38 -0
  122. src/htcli/utils/legacy/__init__.py +12 -0
  123. src/htcli/utils/legacy/colors.py +311 -0
  124. src/htcli/utils/legacy/crypto.py +1176 -0
  125. src/htcli/utils/legacy/formatting.py +452 -0
  126. src/htcli/utils/legacy/interactive.py +306 -0
  127. src/htcli/utils/legacy/subnet_manifest.py +265 -0
  128. src/htcli/utils/legacy/validation.py +488 -0
  129. src/htcli/utils/logging.py +183 -0
  130. src/htcli/utils/network/__init__.py +20 -0
  131. src/htcli/utils/network/subnet.py +344 -0
  132. src/htcli/utils/prompts.py +27 -0
  133. src/htcli/utils/scale_codec.py +155 -0
  134. src/htcli/utils/validation/__init__.py +57 -0
  135. src/htcli/utils/validation/prompt_validators.py +267 -0
  136. src/htcli/utils/wallet/__init__.py +65 -0
  137. src/htcli/utils/wallet/auth.py +151 -0
  138. src/htcli/utils/wallet/core.py +1069 -0
  139. src/htcli/utils/wallet/crypto.py +1615 -0
  140. 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"