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,639 @@
1
+ from typing import Optional
2
+ from textwrap import dedent
3
+
4
+ import typer
5
+
6
+ from .handlers import (
7
+ activate_subnet_handler, # New RPC-based handlers
8
+ check_activation_handler,
9
+ get_bootnodes_handler,
10
+ get_node_staking_handler,
11
+ get_node_status_handler,
12
+ get_subnet_handler,
13
+ get_subnet_info_handler,
14
+ get_subnet_nodes_handler,
15
+ list_nodes_handler,
16
+ list_subnets_handler,
17
+ owner_accept_handler,
18
+ owner_add_bootnode_access_handler,
19
+ owner_clear_emergency_validator_set_handler,
20
+ owner_remove_bootnode_access_handler,
21
+ owner_add_bootnode_handler,
22
+ owner_remove_bootnode_handler,
23
+ owner_remove_handler,
24
+ owner_set_emergency_validator_set_handler,
25
+ owner_transfer_handler,
26
+ owner_update_handler,
27
+ owner_update_name_handler,
28
+ owner_update_repo_handler,
29
+ pause_subnet_handler,
30
+ register_subnet_handler,
31
+ registration_cost_handler,
32
+ unpause_subnet_handler,
33
+ )
34
+
35
+ app = typer.Typer(
36
+ name="subnet",
37
+ help="""
38
+ Commands for interacting with and managing subnets on the Hypertensor network.
39
+
40
+ View subnet details, register new subnets, and perform administrative actions.
41
+ """,
42
+ no_args_is_help=False,
43
+ )
44
+
45
+
46
+ LIST_SUBNETS_HELP = dedent(
47
+ """\
48
+ List subnets on the network.
49
+
50
+ \b
51
+ Options:
52
+ --all List all subnets (including paused/inactive).
53
+ --active List only active subnets.
54
+ --coldkey Filter subnets owned by a specific coldkey.
55
+
56
+ \b
57
+ Example:
58
+ htcli subnet list --all
59
+ """
60
+ )
61
+
62
+ REGISTER_SUBNET_HELP = dedent(
63
+ """\
64
+ Register a new subnet on the network.
65
+
66
+ \b
67
+ Note:
68
+ Configuration fields (churn_limit, queue_epochs, max_registered_nodes, etc.)
69
+ are not part of registration and must be set separately after registration
70
+ using owner update commands such as:
71
+ htcli subnet update --churn-limit ...
72
+ """
73
+ )
74
+
75
+
76
+ @app.callback(invoke_without_command=True)
77
+ def subnet_main(ctx: typer.Context):
78
+ """Subnet operations."""
79
+ if ctx.invoked_subcommand is None:
80
+ typer.echo(ctx.get_help())
81
+ raise typer.Exit(code=0)
82
+
83
+
84
+ @app.command("list", help=LIST_SUBNETS_HELP)
85
+ def list_subnets(
86
+ all_subnets: bool = typer.Option(
87
+ False, "--all", help="List all subnets on the network"
88
+ ),
89
+ active_only: bool = typer.Option(
90
+ False, "--active", help="Show only active subnets."
91
+ ),
92
+ coldkey: Optional[str] = typer.Option(
93
+ None, "--coldkey", "-c", help="Filter subnets by coldkey address or wallet name"
94
+ ),
95
+ ):
96
+ """List subnets on the network."""
97
+ # from ...ui.display import print_error
98
+ # from ...ui.prompts import HTCLIPrompt
99
+
100
+ # If no mode specified, default to all subnets
101
+ if not all_subnets:
102
+ all_subnets = True
103
+
104
+ # # If no mode specified, ask user for choice
105
+ # if not all_subnets and not coldkey:
106
+ # try:
107
+ # prompt = HTCLIPrompt()
108
+ # mode = prompt.select(
109
+ # "What would you like to list?",
110
+ # choices=[
111
+ # ("all", "All subnets"),
112
+ # ("coldkey", "Subnets for a particular coldkey"),
113
+ # ],
114
+ # default="all",
115
+ # )
116
+ # if mode == "all":
117
+ # all_subnets = True
118
+ # else:
119
+ # # Show available wallets and let user select
120
+ # from ...utils.wallet.crypto import list_keys
121
+
122
+ # wallets = list_keys()
123
+ # if not wallets:
124
+ # print_error("No wallets found. Please create a wallet first.")
125
+ # raise typer.Abort()
126
+ # # Extract coldkeys only (exclude hotkeys)
127
+ # coldkey_list = [w for w in wallets if not w.get("is_hotkey", False)]
128
+
129
+ # if not coldkey_list:
130
+ # print_error("No coldkeys found.")
131
+ # raise typer.Abort()
132
+
133
+ # # Format wallet choices for display
134
+ # coldkey_choices = []
135
+ # for wallet in coldkey_list:
136
+ # name = wallet.get("name", "Unknown")
137
+ # address = wallet.get("ss58_address") or wallet.get(
138
+ # "address", "Unknown"
139
+ # )
140
+ # # Format address like MetaMask style: 0x + first 5 + ... + last 5
141
+ # if address and len(address) > 10:
142
+ # if address.startswith("0x"):
143
+ # clean_address = address[2:]
144
+ # if len(clean_address) > 10:
145
+ # formatted_address = (
146
+ # f"0x{clean_address[:5]}...{clean_address[-5:]}"
147
+ # )
148
+ # else:
149
+ # formatted_address = address
150
+ # else:
151
+ # if len(address) > 10:
152
+ # formatted_address = f"{address[:6]}...{address[-6:]}"
153
+ # else:
154
+ # formatted_address = address
155
+ # else:
156
+ # formatted_address = address
157
+ # display = f"{name} ({formatted_address})"
158
+ # coldkey_choices.append((address, display))
159
+
160
+ # selected_coldkey = prompt.select(
161
+ # "Select a wallet (coldkey)",
162
+ # choices=coldkey_choices,
163
+ # )
164
+ # # Normalize Ethereum addresses to lowercase
165
+ # coldkey = (
166
+ # selected_coldkey.lower()
167
+ # if selected_coldkey.startswith("0x")
168
+ # else selected_coldkey
169
+ # )
170
+ # except (KeyboardInterrupt, EOFError):
171
+ # print_error("Operation cancelled")
172
+ # raise typer.Abort()
173
+ # except typer.Abort:
174
+ # raise
175
+ # except Exception:
176
+ # # If interactive input fails, default to all
177
+ # all_subnets = True
178
+
179
+ # Call handler directly (coldkey filtering commented out for now)
180
+ list_subnets_handler(all_subnets, active_only, coldkey=coldkey)
181
+
182
+
183
+ @app.command("cost")
184
+ def registration_cost():
185
+ """Query current subnet registration cost."""
186
+ registration_cost_handler()
187
+
188
+
189
+ @app.command("register", help=REGISTER_SUBNET_HELP)
190
+ def register_subnet(
191
+ coldkey: Optional[str] = typer.Option(
192
+ None,
193
+ "--coldkey",
194
+ help="The coldkey wallet name or address to sign the transaction with.",
195
+ ),
196
+ name: Optional[str] = typer.Option(
197
+ None, "--name", help="Unique name of the subnet"
198
+ ),
199
+ repo: Optional[str] = typer.Option(
200
+ None, "--repo", help="GitHub or similar link to source code"
201
+ ),
202
+ description: Optional[str] = typer.Option(
203
+ None, "--description", help="Description of the subnet"
204
+ ),
205
+ misc: Optional[str] = typer.Option(
206
+ None, "--misc", help="Miscellaneous information"
207
+ ),
208
+ min_stake: Optional[float] = typer.Option(
209
+ None,
210
+ "--min-stake",
211
+ help="Minimum required stake balance to register a node (in TENSOR, e.g. 100.0 for 100 TENSOR)",
212
+ ),
213
+ max_stake: Optional[float] = typer.Option(
214
+ None,
215
+ "--max-stake",
216
+ help="Maximum allowable stake balance for a subnet node (in TENSOR, e.g. 1000.0 for 1000 TENSOR)",
217
+ ),
218
+ max_cost: Optional[float] = typer.Option(
219
+ None,
220
+ "--max-cost",
221
+ help="Maximum cost willing to pay for registration (in TENSOR, e.g. 800000 for 800k TENSOR)",
222
+ ),
223
+ delegate_stake_percentage: Optional[int] = typer.Option(
224
+ None,
225
+ "--delegate-percentage",
226
+ help="Percentage of emissions given to delegate stakers (5-95, e.g. 20 for 20%)",
227
+ ),
228
+ # Note: Configuration fields like churn_limit, queue_epochs, max_registered_nodes, etc.
229
+ # are NOT part of RegistrationSubnetData and cannot be set during registration.
230
+ # They must be set separately after registration using owner update commands.
231
+ initial_coldkeys: Optional[str] = typer.Option(
232
+ None,
233
+ "--initial-coldkeys",
234
+ help="Comma-separated list of at least 3 initial coldkey addresses (required for registration, becomes permissionless after activation)",
235
+ ),
236
+ key_types: Optional[str] = typer.Option(
237
+ None,
238
+ "--key-types",
239
+ help="Comma-separated key types (Ed25519,Ecdsa,Secp256k1,Rsa) - required when using --initial-coldkeys",
240
+ ),
241
+ bootnodes: Optional[str] = typer.Option(
242
+ None,
243
+ "--bootnodes",
244
+ help="Comma-separated list of bootnode addresses (e.g., /ip4/1.2.3.4/tcp/30333,/dns4/node.example/tcp/30333)",
245
+ ),
246
+ ):
247
+ """Register a new subnet on the network."""
248
+ register_subnet_handler(
249
+ coldkey=coldkey,
250
+ name=name,
251
+ repo=repo,
252
+ description=description,
253
+ misc=misc,
254
+ min_stake=min_stake,
255
+ max_stake=max_stake,
256
+ max_cost=max_cost,
257
+ delegate_stake_percentage=delegate_stake_percentage,
258
+ initial_coldkeys=initial_coldkeys,
259
+ key_types=key_types,
260
+ bootnodes=bootnodes,
261
+ )
262
+
263
+
264
+ @app.command("check-activation")
265
+ def check_activation(
266
+ subnet_id: Optional[int] = typer.Option(
267
+ None,
268
+ "--subnet-id",
269
+ "--subnetid",
270
+ help="The unique ID (UID) of the subnet to check activation requirements for.",
271
+ ),
272
+ ):
273
+ """Check if a subnet meets activation requirements including minimum registration epochs."""
274
+ check_activation_handler(subnet_id)
275
+
276
+
277
+ @app.command("activate")
278
+ def activate_subnet(
279
+ subnet_id: Optional[int] = typer.Option(
280
+ None,
281
+ "--subnet-id",
282
+ "--subnetid",
283
+ help="The unique ID (UID) of the subnet to activate.",
284
+ ),
285
+ coldkey: Optional[str] = typer.Option(
286
+ None,
287
+ "--coldkey",
288
+ help="The coldkey wallet name or address to sign the transaction with.",
289
+ ),
290
+ ):
291
+ """Activate a registered subnet. (owner only)"""
292
+ activate_subnet_handler(subnet_id, coldkey=coldkey)
293
+
294
+
295
+ @app.command("pause")
296
+ def pause_subnet(
297
+ subnet_id: Optional[int] = typer.Option(
298
+ None,
299
+ "--subnet-id",
300
+ "--subnetid",
301
+ help="The unique ID (UID) of the subnet to pause.",
302
+ ),
303
+ coldkey: Optional[str] = typer.Option(
304
+ None,
305
+ "--coldkey",
306
+ "-c",
307
+ help="The coldkey wallet name or address to sign the transaction with.",
308
+ ),
309
+ ):
310
+ """Pause a subnet's operations. (owner only)"""
311
+ pause_subnet_handler(subnet_id, coldkey=coldkey)
312
+
313
+
314
+ @app.command("unpause")
315
+ def unpause_subnet(
316
+ subnet_id: Optional[int] = typer.Option(
317
+ None,
318
+ "--subnet-id",
319
+ "--subnetid",
320
+ help="The unique ID (UID) of the subnet to unpause.",
321
+ ),
322
+ coldkey: Optional[str] = typer.Option(
323
+ None,
324
+ "--coldkey",
325
+ "-c",
326
+ help="The coldkey wallet name or address to sign the transaction with.",
327
+ ),
328
+ ):
329
+ """Unpause a subnet's operations. (owner only)"""
330
+ unpause_subnet_handler(subnet_id, coldkey=coldkey)
331
+
332
+
333
+ @app.command("update")
334
+ def owner_update(
335
+ subnet_id: Optional[int] = typer.Option(
336
+ None, "--subnet-id", "--subnetid", help="The UID of the subnet to update."
337
+ ),
338
+ new_name: Optional[str] = typer.Option(
339
+ None, "--name", help="The new name for the subnet."
340
+ ),
341
+ new_repo: Optional[str] = typer.Option(
342
+ None, "--repo", help="The new repository URL for the subnet."
343
+ ),
344
+ target_node_registrations: Optional[int] = typer.Option(
345
+ None,
346
+ "--target-node-registrations",
347
+ help="Target number of node registrations per epoch.",
348
+ ),
349
+ node_burn_rate_alpha: Optional[int] = typer.Option(
350
+ None,
351
+ "--node-burn-rate-alpha",
352
+ help="Node burn rate alpha value (in wei format).",
353
+ ),
354
+ queue_immunity_epochs: Optional[int] = typer.Option(
355
+ None,
356
+ "--queue-immunity-epochs",
357
+ help="Number of epochs nodes are immune from queue removal.",
358
+ ),
359
+ min_weight_decrease_threshold: Optional[int] = typer.Option(
360
+ None,
361
+ "--min-weight-decrease-threshold",
362
+ help="Minimum weight decrease reputation threshold (in wei format).",
363
+ ),
364
+ min_node_reputation: Optional[int] = typer.Option(
365
+ None,
366
+ "--min-node-reputation",
367
+ help="Minimum subnet node reputation (in wei format).",
368
+ ),
369
+ absent_reputation_penalty: Optional[int] = typer.Option(
370
+ None,
371
+ "--absent-reputation-penalty",
372
+ help="Absent decrease reputation factor (in wei format).",
373
+ ),
374
+ included_reputation_boost: Optional[int] = typer.Option(
375
+ None,
376
+ "--included-reputation-boost",
377
+ help="Included increase reputation factor (in wei format).",
378
+ ),
379
+ below_min_weight_penalty: Optional[int] = typer.Option(
380
+ None,
381
+ "--below-min-weight-penalty",
382
+ help="Below-min weight decrease reputation factor (in wei format).",
383
+ ),
384
+ non_attestor_penalty: Optional[int] = typer.Option(
385
+ None,
386
+ "--non-attestor-penalty",
387
+ help="Non-attestor decrease reputation factor (in wei format).",
388
+ ),
389
+ validator_absent_penalty: Optional[int] = typer.Option(
390
+ None,
391
+ "--validator-absent-penalty",
392
+ help="Validator absent decrease reputation factor (in wei format).",
393
+ ),
394
+ validator_non_consensus_penalty: Optional[int] = typer.Option(
395
+ None,
396
+ "--validator-non-consensus-penalty",
397
+ help="Validator non-consensus decrease reputation factor (in wei format).",
398
+ ),
399
+ non_consensus_attestor_penalty: Optional[int] = typer.Option(
400
+ None,
401
+ "--non-consensus-attestor-penalty",
402
+ help="Non-consensus attestor decrease reputation factor (in wei format).",
403
+ ),
404
+ coldkey: Optional[str] = typer.Option(
405
+ None,
406
+ "--coldkey",
407
+ "-c",
408
+ help="The coldkey wallet name or address to sign the transaction with.",
409
+ ),
410
+ ):
411
+ """Update subnet properties. (owner only)"""
412
+ owner_update_handler(
413
+ subnet_id,
414
+ new_name,
415
+ new_repo,
416
+ target_node_registrations=target_node_registrations,
417
+ node_burn_rate_alpha=node_burn_rate_alpha,
418
+ queue_immunity_epochs=queue_immunity_epochs,
419
+ min_weight_decrease_threshold=min_weight_decrease_threshold,
420
+ min_node_reputation=min_node_reputation,
421
+ absent_reputation_penalty=absent_reputation_penalty,
422
+ included_reputation_boost=included_reputation_boost,
423
+ below_min_weight_penalty=below_min_weight_penalty,
424
+ non_attestor_penalty=non_attestor_penalty,
425
+ validator_absent_penalty=validator_absent_penalty,
426
+ validator_non_consensus_penalty=validator_non_consensus_penalty,
427
+ non_consensus_attestor_penalty=non_consensus_attestor_penalty,
428
+ coldkey=coldkey,
429
+ )
430
+
431
+
432
+ @app.command("transfer")
433
+ def owner_transfer(
434
+ subnet_id: Optional[int] = typer.Option(
435
+ None, "--subnet-id", "--subnetid", help="The UID of the subnet to transfer."
436
+ ),
437
+ new_owner: Optional[str] = typer.Option(
438
+ None,
439
+ "--to",
440
+ help="The EVM address or wallet name of the new owner (e.g., 0x... or wallet-name).",
441
+ ),
442
+ coldkey: Optional[str] = typer.Option(
443
+ None,
444
+ "--coldkey",
445
+ "-c",
446
+ help="The coldkey wallet name or address to sign the transaction with.",
447
+ ),
448
+ ):
449
+ """Transfer ownership of a subnet to a new address. (owner only)"""
450
+ owner_transfer_handler(subnet_id, new_owner, coldkey=coldkey)
451
+
452
+
453
+ @app.command("accept")
454
+ def owner_accept(
455
+ subnet_id: Optional[int] = typer.Option(
456
+ None,
457
+ "--subnet-id",
458
+ "--subnetid",
459
+ help="The UID of the subnet to accept ownership of.",
460
+ ),
461
+ coldkey: Optional[str] = typer.Option(
462
+ None,
463
+ "--coldkey",
464
+ "-c",
465
+ help="The coldkey wallet name or address to sign the transaction with.",
466
+ ),
467
+ ):
468
+ """Accept a pending ownership transfer for a subnet. (owner only)"""
469
+ owner_accept_handler(subnet_id, coldkey=coldkey)
470
+
471
+
472
+ @app.command("remove")
473
+ def owner_remove(
474
+ subnet_id: Optional[int] = typer.Option(
475
+ None, "--subnet-id", "--subnetid", help="The UID of the subnet to remove."
476
+ ),
477
+ coldkey: Optional[str] = typer.Option(
478
+ None,
479
+ "--coldkey",
480
+ "-c",
481
+ help="The coldkey wallet name or address to sign the transaction with.",
482
+ ),
483
+ ):
484
+ """Permanently remove a subnet you own from the network. (owner only)"""
485
+ owner_remove_handler(subnet_id, coldkey=coldkey)
486
+
487
+
488
+ @app.command("set-emergency-validator-set")
489
+ def owner_set_emergency_validator_set(
490
+ subnet_id: Optional[int] = typer.Option(
491
+ None, "--subnet-id", "--subnetid", help="The UID of the subnet."
492
+ ),
493
+ node_ids: Optional[str] = typer.Option(
494
+ None,
495
+ "--node-ids",
496
+ help="Comma-separated list of subnet node IDs for emergency validator set.",
497
+ ),
498
+ coldkey: Optional[str] = typer.Option(
499
+ None,
500
+ "--coldkey",
501
+ "-c",
502
+ help="The coldkey wallet name or address to sign the transaction with.",
503
+ ),
504
+ ):
505
+ """Set emergency validator set for a subnet. (owner only)"""
506
+ owner_set_emergency_validator_set_handler(subnet_id, node_ids, coldkey=coldkey)
507
+
508
+
509
+ @app.command("clear-emergency-validator-set")
510
+ def owner_clear_emergency_validator_set(
511
+ subnet_id: Optional[int] = typer.Option(
512
+ None, "--subnet-id", "--subnetid", help="The UID of the subnet."
513
+ ),
514
+ coldkey: Optional[str] = typer.Option(
515
+ None,
516
+ "--coldkey",
517
+ "-c",
518
+ help="The coldkey wallet name or address to sign the transaction with.",
519
+ ),
520
+ ):
521
+ """Clear emergency validator set for a subnet. (owner only)"""
522
+ owner_clear_emergency_validator_set_handler(subnet_id, coldkey=coldkey)
523
+
524
+
525
+ @app.command("add-bootnode-access")
526
+ def owner_add_bootnode_access(
527
+ subnet_id: Optional[int] = typer.Option(
528
+ None, "--subnet-id", "--subnetid", help="The UID of the subnet."
529
+ ),
530
+ account: Optional[str] = typer.Option(
531
+ None, "--account", help="Account address to grant bootnode access to."
532
+ ),
533
+ coldkey: Optional[str] = typer.Option(
534
+ None,
535
+ "--coldkey",
536
+ "-c",
537
+ help="The coldkey wallet name or address to sign the transaction with.",
538
+ ),
539
+ ):
540
+ """Grant bootnode access to an account. (owner only)"""
541
+ owner_add_bootnode_access_handler(subnet_id, account, coldkey=coldkey)
542
+
543
+
544
+ @app.command("remove-bootnode-access")
545
+ def owner_remove_bootnode_access(
546
+ subnet_id: Optional[int] = typer.Option(
547
+ None, "--subnet-id", "--subnetid", help="The UID of the subnet."
548
+ ),
549
+ account: Optional[str] = typer.Option(
550
+ None, "--account", help="Account address to revoke bootnode access from."
551
+ ),
552
+ coldkey: Optional[str] = typer.Option(
553
+ None,
554
+ "--coldkey",
555
+ "-c",
556
+ help="The coldkey wallet name or address to sign the transaction with.",
557
+ ),
558
+ ):
559
+ """Revoke bootnode access from an account. (owner only)"""
560
+ owner_remove_bootnode_access_handler(subnet_id, account, coldkey=coldkey)
561
+
562
+
563
+ @app.command("add-bootnode")
564
+ def owner_add_bootnode(
565
+ subnet_id: Optional[int] = typer.Option(
566
+ None, "--subnet-id", "--subnetid", help="The UID of the subnet."
567
+ ),
568
+ bootnode_address: Optional[str] = typer.Option(
569
+ None,
570
+ "--bootnode-address",
571
+ help="The bootnode multiaddress to add (e.g., /ip4/1.2.3.4/tcp/30333/p2p/...).",
572
+ ),
573
+ coldkey: Optional[str] = typer.Option(
574
+ None,
575
+ "--coldkey",
576
+ "-c",
577
+ help="The coldkey wallet name or address to sign the transaction with.",
578
+ ),
579
+ ):
580
+ """Add a bootnode address to the subnet. (owner only)"""
581
+ owner_add_bootnode_handler(subnet_id, bootnode_address, coldkey=coldkey)
582
+
583
+
584
+ @app.command("remove-bootnode")
585
+ def owner_remove_bootnode(
586
+ subnet_id: Optional[int] = typer.Option(
587
+ None, "--subnet-id", "--subnetid", help="The UID of the subnet."
588
+ ),
589
+ bootnode_address: Optional[str] = typer.Option(
590
+ None,
591
+ "--bootnode-address",
592
+ help="The bootnode multiaddress to remove.",
593
+ ),
594
+ coldkey: Optional[str] = typer.Option(
595
+ None,
596
+ "--coldkey",
597
+ "-c",
598
+ help="The coldkey wallet name or address to sign the transaction with.",
599
+ ),
600
+ ):
601
+ """Remove a bootnode address from the subnet. (owner only)"""
602
+ owner_remove_bootnode_handler(subnet_id, bootnode_address, coldkey=coldkey)
603
+
604
+
605
+ # New RPC-based commands
606
+ @app.command("info")
607
+ def get_subnet_info(
608
+ subnet_id: Optional[int] = typer.Option(
609
+ None, "--subnet-id", "--subnetid", help="The subnet ID to get information for"
610
+ ),
611
+ all_subnets: bool = typer.Option(
612
+ False, "--all", help="Show information for every subnet on the network"
613
+ ),
614
+ ):
615
+ """Get detailed information about a specific subnet or all subnets using RPC."""
616
+ get_subnet_info_handler(subnet_id, all_subnets=all_subnets)
617
+
618
+
619
+ @app.command("nodes")
620
+ def get_subnet_nodes(
621
+ subnet_id: Optional[int] = typer.Option(
622
+ None, "--subnet-id", "--subnetid", help="The subnet ID to get nodes for"
623
+ ),
624
+ ):
625
+ """Get all nodes in a specific subnet using RPC."""
626
+ get_subnet_nodes_handler(subnet_id, None)
627
+
628
+
629
+ @app.command("bootnodes")
630
+ def get_bootnodes(
631
+ subnet_id: Optional[int] = typer.Option(
632
+ None, "--subnet-id", "--subnetid", help="The subnet ID to get bootnodes for"
633
+ ),
634
+ ):
635
+ """Get bootnodes for a specific subnet using RPC."""
636
+ get_bootnodes_handler(subnet_id)
637
+
638
+
639
+ __all__ = ["app"]