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,286 @@
1
+ """
2
+ Temporary patches for blockchain SCALE encoding quirks.
3
+ """
4
+
5
+ from collections.abc import Iterable
6
+ from contextlib import contextmanager
7
+ from typing import Any
8
+
9
+ from scalecodec.types import CompactU32, Map
10
+
11
+ # Import logger for debugging
12
+ try:
13
+ from ...utils.logging import get_logger
14
+
15
+ logger = get_logger(__name__)
16
+ except ImportError:
17
+ import logging
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ _ORIGINAL_MAP_PROCESS_ENCODE = Map.process_encode
22
+
23
+
24
+ def _should_patch_btreemap_accountid_u32(value: Any, map_instance: Map) -> bool:
25
+ """
26
+ Determine whether we need to patch the Map encoder for BTreeMap<AccountId, u32>.
27
+
28
+ The issue arises when map_instance.map_key/map_value are None because the
29
+ runtime metadata does not correctly expose the key/value types, which happens
30
+ for BTreeMap<AccountId, u32> in the Hypertensor runtime.
31
+ """
32
+ if map_instance.map_key and map_instance.map_value:
33
+ return False
34
+
35
+ # Value must be a list/tuple of 2-tuples
36
+ if not isinstance(value, Iterable):
37
+ return False
38
+
39
+ try:
40
+ first_item = next(iter(value))
41
+ except StopIteration:
42
+ # Empty map - no need to patch
43
+ return False
44
+
45
+ if not isinstance(first_item, (tuple, list)) or len(first_item) != 2:
46
+ return False
47
+
48
+ key_candidate, value_candidate = first_item
49
+
50
+ # Keys should be 20-byte addresses (bytes)
51
+ if not isinstance(key_candidate, (bytes, bytearray)) or len(key_candidate) != 20:
52
+ return False
53
+
54
+ # Values should be ints (u32)
55
+ if not isinstance(value_candidate, int):
56
+ return False
57
+
58
+ return True
59
+
60
+
61
+ def _should_patch_btreemap_u32_u32(value: Any, map_instance: Map) -> bool:
62
+ """Detect BTreeMap<u32, u32> values when metadata lacks key/value types."""
63
+ if map_instance.map_key and map_instance.map_value:
64
+ return False
65
+
66
+ if not isinstance(value, Iterable):
67
+ return False
68
+
69
+ try:
70
+ first_item = next(iter(value))
71
+ except StopIteration:
72
+ return False
73
+
74
+ if not isinstance(first_item, (tuple, list)) or len(first_item) != 2:
75
+ return False
76
+
77
+ key_candidate, value_candidate = first_item
78
+ return isinstance(key_candidate, int) and isinstance(value_candidate, int)
79
+
80
+
81
+ def _should_patch_btreemap_bytes_bytes(value: Any, map_instance: Map) -> bool:
82
+ """
83
+ Detect BTreeMap<PeerId, BoundedVec<u8, ...>> values when metadata lacks types.
84
+
85
+ Both PeerId and BoundedVec<u8, ...> are encoded like Vec<u8> in the deployed
86
+ Hypertensor runtime metadata.
87
+ """
88
+ if map_instance.map_key and map_instance.map_value:
89
+ return False
90
+
91
+ if not isinstance(value, Iterable):
92
+ return False
93
+
94
+ try:
95
+ first_item = next(iter(value))
96
+ except StopIteration:
97
+ return False
98
+
99
+ if not isinstance(first_item, (tuple, list)) or len(first_item) != 2:
100
+ return False
101
+
102
+ key_candidate, value_candidate = first_item
103
+ return isinstance(key_candidate, (bytes, bytearray)) and isinstance(
104
+ value_candidate, (bytes, bytearray)
105
+ )
106
+
107
+
108
+ def _encode_compact_bytes(value: bytes | bytearray) -> bytes:
109
+ element_count_compact = CompactU32()
110
+ element_count_compact.encode(len(value))
111
+ return element_count_compact.data + bytes(value)
112
+
113
+
114
+ def _patched_process_encode(self: Map, value: Any) -> bytes:
115
+ """
116
+ Patched Map.process_encode that handles BTreeMap<AccountId, u32>.
117
+
118
+ This patch is needed because substrate-interface/scalecodec may not correctly
119
+ identify BTreeMap<AccountId, u32> from runtime metadata, especially when
120
+ AccountId is defined as [u8; 20].
121
+
122
+ Encoding format:
123
+ - Compact<u32> length
124
+ - For each entry (sorted by key):
125
+ - [u8; 20] AccountId (20 raw bytes)
126
+ - u32 value (4 bytes, little-endian)
127
+ """
128
+ if _should_patch_btreemap_u32_u32(value, self):
129
+ value_list = list(value)
130
+ value_list.sort(key=lambda x: x[0])
131
+
132
+ element_count_compact = CompactU32()
133
+ element_count_compact.encode(len(value_list))
134
+ data = element_count_compact.data
135
+
136
+ self.map_key = "u32"
137
+ self.map_value = "u32"
138
+
139
+ for idx, item in enumerate(value_list):
140
+ if not isinstance(item, (tuple, list)) or len(item) != 2:
141
+ raise ValueError(
142
+ f"BTreeMap entry {idx} must be (key, value) tuple, got: {type(item)}"
143
+ )
144
+
145
+ item_key, item_value = item
146
+ if not isinstance(item_key, int) or not isinstance(item_value, int):
147
+ raise ValueError(
148
+ f"BTreeMap<u32, u32> entry {idx} must contain ints, got: {item}"
149
+ )
150
+ if not 0 <= item_key <= 4294967295:
151
+ raise ValueError(f"u32 key at index {idx} out of range: {item_key}")
152
+ if not 0 <= item_value <= 4294967295:
153
+ raise ValueError(f"u32 value at index {idx} out of range: {item_value}")
154
+
155
+ data += item_key.to_bytes(4, byteorder="little", signed=False)
156
+ data += item_value.to_bytes(4, byteorder="little", signed=False)
157
+
158
+ return data
159
+
160
+ if _should_patch_btreemap_bytes_bytes(value, self):
161
+ value_list = list(value)
162
+ value_list.sort(key=lambda x: bytes(x[0]))
163
+
164
+ element_count_compact = CompactU32()
165
+ element_count_compact.encode(len(value_list))
166
+ data = element_count_compact.data
167
+
168
+ self.map_key = "Vec<u8>"
169
+ self.map_value = "Vec<u8>"
170
+
171
+ for idx, item in enumerate(value_list):
172
+ if not isinstance(item, (tuple, list)) or len(item) != 2:
173
+ raise ValueError(
174
+ f"BTreeMap entry {idx} must be (key, value) tuple, got: {type(item)}"
175
+ )
176
+
177
+ item_key, item_value = item
178
+ if not isinstance(item_key, (bytes, bytearray)) or not isinstance(
179
+ item_value, (bytes, bytearray)
180
+ ):
181
+ raise ValueError(
182
+ f"BTreeMap<bytes, bytes> entry {idx} must contain bytes, got: {item}"
183
+ )
184
+
185
+ data += _encode_compact_bytes(item_key)
186
+ data += _encode_compact_bytes(item_value)
187
+
188
+ return data
189
+
190
+ if not _should_patch_btreemap_accountid_u32(value, self):
191
+ return _ORIGINAL_MAP_PROCESS_ENCODE(self, value)
192
+
193
+ logger.debug("Patching BTreeMap<AccountId, u32> encoding")
194
+
195
+ # Ensure we have a concrete list (value might be set, generator, etc.)
196
+ # BTreeMap entries must be sorted by key for deterministic encoding
197
+ value_list = list(value)
198
+
199
+ # Sort by key (address bytes) to match BTreeMap ordering
200
+ # This is critical - BTreeMap is always sorted!
201
+ value_list.sort(
202
+ key=lambda x: x[0] if isinstance(x, (tuple, list)) and len(x) >= 1 else b""
203
+ )
204
+
205
+ logger.debug(f"Encoding BTreeMap with {len(value_list)} entries")
206
+
207
+ # Encode length as Compact<u32>
208
+ element_count_compact = CompactU32()
209
+ element_count_compact.encode(len(value_list))
210
+ data = element_count_compact.data
211
+
212
+ # Set type information for scalecodec
213
+ self.map_key = "[u8; 20]"
214
+ self.map_value = "u32"
215
+
216
+ # Encode each entry
217
+ for idx, item in enumerate(value_list):
218
+ if not isinstance(item, (tuple, list)) or len(item) != 2:
219
+ raise ValueError(
220
+ f"BTreeMap entry {idx} must be (key, value) tuple, got: {type(item)}"
221
+ )
222
+
223
+ item_key, item_value = item
224
+
225
+ # Encode key: [u8; 20] AccountId
226
+ if isinstance(item_key, bytes):
227
+ if len(item_key) != 20:
228
+ raise ValueError(
229
+ f"AccountId at index {idx} must be exactly 20 bytes, got {len(item_key)} bytes"
230
+ )
231
+ # [u8; 20] is encoded as raw 20 bytes (no length prefix)
232
+ data += item_key
233
+ elif isinstance(item_key, bytearray):
234
+ if len(item_key) != 20:
235
+ raise ValueError(
236
+ f"AccountId at index {idx} must be exactly 20 bytes, got {len(item_key)} bytes"
237
+ )
238
+ data += bytes(item_key)
239
+ else:
240
+ # Try to use scalecodec to encode (fallback)
241
+ try:
242
+ key_obj = self.runtime_config.create_scale_object(
243
+ type_string="[u8; 20]", metadata=self.metadata
244
+ )
245
+ data += key_obj.encode(item_key)
246
+ except Exception as e:
247
+ raise ValueError(
248
+ f"Failed to encode AccountId key at index {idx}: {item_key}, error: {e}"
249
+ ) from e
250
+
251
+ # Encode value: u32 (4 bytes, little-endian)
252
+ if isinstance(item_value, int):
253
+ # Ensure it fits in u32 range
254
+ if item_value < 0 or item_value > 4294967295:
255
+ raise ValueError(
256
+ f"u32 value at index {idx} must be 0-4294967295, got {item_value}"
257
+ )
258
+ # u32 is 4 bytes, little-endian
259
+ data += item_value.to_bytes(4, byteorder="little", signed=False)
260
+ else:
261
+ # Try to use scalecodec to encode (fallback)
262
+ try:
263
+ value_obj = self.runtime_config.create_scale_object(
264
+ type_string="u32", metadata=self.metadata
265
+ )
266
+ data += value_obj.encode(item_value)
267
+ except Exception as e:
268
+ raise ValueError(
269
+ f"Failed to encode u32 value at index {idx}: {item_value}, error: {e}"
270
+ ) from e
271
+
272
+ logger.debug(f"Successfully encoded BTreeMap<AccountId, u32> ({len(data)} bytes)")
273
+ return data
274
+
275
+
276
+ @contextmanager
277
+ def patch_btreemap_accountid_u32_encoder():
278
+ """
279
+ Context manager that patches scalecodec Map.process_encode to handle
280
+ BTreeMap<AccountId, u32> encoding when metadata lacks type information.
281
+ """
282
+ Map.process_encode = _patched_process_encode
283
+ try:
284
+ yield
285
+ finally:
286
+ Map.process_encode = _ORIGINAL_MAP_PROCESS_ENCODE
@@ -0,0 +1,186 @@
1
+ """
2
+ Peer ID encoding utilities for Hypertensor blockchain.
3
+
4
+ Handles proper encoding of libp2p peer IDs which are base58-encoded multihashes.
5
+ """
6
+
7
+ from typing import Union
8
+
9
+ import base58
10
+
11
+
12
+ def encode_peer_id(peer_id: Union[str, bytes]) -> bytes:
13
+ """
14
+ Encode a peer ID for blockchain submission.
15
+
16
+ Args:
17
+ peer_id: Peer ID as string (base58 multihash) or bytes
18
+
19
+ Returns:
20
+ Encoded peer ID as bytes for blockchain submission
21
+
22
+ Raises:
23
+ ValueError: If peer ID format is invalid
24
+ """
25
+ if isinstance(peer_id, bytes):
26
+ # Already bytes, validate it's base58 decodable
27
+ try:
28
+ # Try to decode and re-encode to validate
29
+ decoded = base58.b58decode(peer_id)
30
+ return peer_id
31
+ except Exception:
32
+ raise ValueError(f"Invalid peer ID bytes: {peer_id}")
33
+
34
+ if isinstance(peer_id, str):
35
+ # String peer ID - should be base58 multihash
36
+ if not peer_id:
37
+ raise ValueError("Peer ID cannot be empty")
38
+
39
+ # Validate it's a valid base58 string
40
+ try:
41
+ decoded = base58.b58decode(peer_id)
42
+ # Re-encode to get proper bytes representation
43
+ return peer_id.encode("utf-8")
44
+ except Exception as e:
45
+ raise ValueError(f"Invalid peer ID format '{peer_id}': {e}")
46
+
47
+ raise ValueError(f"Peer ID must be string or bytes, got {type(peer_id)}")
48
+
49
+
50
+ def decode_peer_id(encoded_peer_id: bytes) -> str:
51
+ """
52
+ Decode a peer ID from blockchain response.
53
+
54
+ Args:
55
+ encoded_peer_id: Peer ID bytes from blockchain
56
+
57
+ Returns:
58
+ Peer ID as base58 string
59
+
60
+ Raises:
61
+ ValueError: If decoding fails
62
+ """
63
+ if not isinstance(encoded_peer_id, bytes):
64
+ raise ValueError(f"Encoded peer ID must be bytes, got {type(encoded_peer_id)}")
65
+
66
+ try:
67
+ # Try to decode as UTF-8 first (most common case)
68
+ peer_id_str = encoded_peer_id.decode("utf-8")
69
+
70
+ # Validate it's a valid base58 string
71
+ base58.b58decode(peer_id_str)
72
+ return peer_id_str
73
+
74
+ except UnicodeDecodeError:
75
+ # If not UTF-8, treat as raw bytes and encode to base58
76
+ return base58.b58encode(encoded_peer_id).decode("ascii")
77
+ except Exception as e:
78
+ raise ValueError(f"Failed to decode peer ID: {e}")
79
+
80
+
81
+ def validate_peer_id_format(peer_id: str) -> bool:
82
+ """
83
+ Validate that a peer ID is in correct format.
84
+
85
+ Args:
86
+ peer_id: Peer ID string to validate
87
+
88
+ Returns:
89
+ True if valid, False otherwise
90
+ """
91
+ if not isinstance(peer_id, str) or not peer_id:
92
+ return False
93
+
94
+ try:
95
+ # Check if it's a valid base58 string
96
+ decoded = base58.b58decode(peer_id)
97
+
98
+ # Basic validation - peer IDs are typically 34-46 characters
99
+ # and start with 'Qm' for SHA256 multihash
100
+ if len(peer_id) < 30 or len(peer_id) > 60:
101
+ return False
102
+
103
+ # Common peer ID prefixes for libp2p
104
+ valid_prefixes = ["Qm", "12D3", "16Uiu2HA"]
105
+ if not any(peer_id.startswith(prefix) for prefix in valid_prefixes):
106
+ return False
107
+
108
+ return True
109
+
110
+ except Exception:
111
+ return False
112
+
113
+
114
+ def generate_test_peer_id(seed: int = 0) -> str:
115
+ """
116
+ Generate a test peer ID for testing purposes.
117
+
118
+ Args:
119
+ seed: Seed for deterministic generation
120
+
121
+ Returns:
122
+ Valid test peer ID string
123
+ """
124
+ import hashlib
125
+
126
+ # Generate deterministic test data
127
+ test_data = f"test_peer_id_{seed}".encode()
128
+ hash_bytes = hashlib.sha256(test_data).digest()
129
+
130
+ # Create a valid multihash (Qm prefix indicates SHA256)
131
+ # Format: [0x12, 0x20, <32-byte-hash>]
132
+ multihash = b"\x12\x20" + hash_bytes
133
+
134
+ # Encode as base58
135
+ peer_id = base58.b58encode(multihash).decode("ascii")
136
+
137
+ return peer_id
138
+
139
+
140
+ # Common peer ID patterns for testing
141
+ TEST_PEER_IDS = {
142
+ "alice": "QmAliceTest1234567890abcdefghijklmnopqrstuvwxyz",
143
+ "bob": "QmBobTest1234567890abcdefghijklmnopqrstuvwxyz",
144
+ "charlie": "QmCharlieTest1234567890abcdefghijklmnopqrstuvwxyz",
145
+ "bootnode": "QmBootnodeTest1234567890abcdefghijklmnopqrstuvwxyz",
146
+ }
147
+
148
+
149
+ def get_test_peer_id(name: str = "alice") -> str:
150
+ """
151
+ Get a predefined test peer ID.
152
+
153
+ Args:
154
+ name: Name of test peer (alice, bob, charlie, bootnode)
155
+
156
+ Returns:
157
+ Test peer ID string
158
+ """
159
+ return TEST_PEER_IDS.get(name, generate_test_peer_id(hash(name) % 1000))
160
+
161
+
162
+ # Example usage and validation
163
+ if __name__ == "__main__":
164
+ # Test the utilities
165
+ test_peer = "QmTestPeerID1234567890abcdefghijklmnopqrstuvwxyz"
166
+
167
+ print("Testing peer ID utilities...")
168
+
169
+ # Test encoding
170
+ encoded = encode_peer_id(test_peer)
171
+ print(f"Original: {test_peer}")
172
+ print(f"Encoded: {encoded}")
173
+
174
+ # Test decoding
175
+ decoded = decode_peer_id(encoded)
176
+ print(f"Decoded: {decoded}")
177
+
178
+ # Test validation
179
+ is_valid = validate_peer_id_format(test_peer)
180
+ print(f"Valid: {is_valid}")
181
+
182
+ # Test generation
183
+ generated = generate_test_peer_id(42)
184
+ print(f"Generated: {generated}")
185
+
186
+ print("✅ All tests passed!")