bittensor-cli 9.2.0__py3-none-any.whl → 9.4.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.
- bittensor_cli/cli.py +601 -133
- bittensor_cli/src/__init__.py +444 -465
- bittensor_cli/src/bittensor/chain_data.py +6 -2
- bittensor_cli/src/bittensor/extrinsics/registration.py +47 -23
- bittensor_cli/src/bittensor/extrinsics/root.py +10 -11
- bittensor_cli/src/bittensor/extrinsics/transfer.py +5 -3
- bittensor_cli/src/bittensor/subtensor_interface.py +59 -5
- bittensor_cli/src/bittensor/utils.py +6 -2
- bittensor_cli/src/commands/stake/add.py +127 -98
- bittensor_cli/src/commands/stake/children_hotkeys.py +120 -79
- bittensor_cli/src/commands/stake/list.py +54 -20
- bittensor_cli/src/commands/stake/move.py +15 -12
- bittensor_cli/src/commands/stake/remove.py +101 -80
- bittensor_cli/src/commands/subnets/price.py +11 -9
- bittensor_cli/src/commands/subnets/subnets.py +334 -81
- bittensor_cli/src/commands/sudo.py +76 -22
- bittensor_cli/src/commands/wallets.py +656 -40
- bittensor_cli/src/commands/weights.py +21 -11
- bittensor_cli/version.py +14 -11
- {bittensor_cli-9.2.0.dist-info → bittensor_cli-9.4.0.dist-info}/METADATA +8 -9
- bittensor_cli-9.4.0.dist-info/RECORD +35 -0
- {bittensor_cli-9.2.0.dist-info → bittensor_cli-9.4.0.dist-info}/WHEEL +1 -1
- bittensor_cli-9.2.0.dist-info/RECORD +0 -35
- {bittensor_cli-9.2.0.dist-info → bittensor_cli-9.4.0.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-9.2.0.dist-info → bittensor_cli-9.4.0.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
|
|
1
1
|
import asyncio
|
2
2
|
import itertools
|
3
|
+
import json
|
3
4
|
import os
|
4
5
|
from collections import defaultdict
|
5
6
|
from typing import Generator, Optional
|
@@ -14,8 +15,9 @@ from rich.align import Align
|
|
14
15
|
from rich.table import Column, Table
|
15
16
|
from rich.tree import Tree
|
16
17
|
from rich.padding import Padding
|
18
|
+
from rich.prompt import Confirm
|
17
19
|
|
18
|
-
from bittensor_cli.src import COLOR_PALETTE
|
20
|
+
from bittensor_cli.src import COLOR_PALETTE, COLORS, Constants
|
19
21
|
from bittensor_cli.src.bittensor import utils
|
20
22
|
from bittensor_cli.src.bittensor.balances import Balance
|
21
23
|
from bittensor_cli.src.bittensor.chain_data import (
|
@@ -34,6 +36,7 @@ from bittensor_cli.src.bittensor.utils import (
|
|
34
36
|
console,
|
35
37
|
convert_blocks_to_time,
|
36
38
|
err_console,
|
39
|
+
json_console,
|
37
40
|
print_error,
|
38
41
|
print_verbose,
|
39
42
|
get_all_wallets_for_path,
|
@@ -44,9 +47,78 @@ from bittensor_cli.src.bittensor.utils import (
|
|
44
47
|
millify_tao,
|
45
48
|
unlock_key,
|
46
49
|
WalletLike,
|
50
|
+
blocks_to_duration,
|
51
|
+
decode_account_id,
|
47
52
|
)
|
48
53
|
|
49
54
|
|
55
|
+
async def associate_hotkey(
|
56
|
+
wallet: Wallet,
|
57
|
+
subtensor: SubtensorInterface,
|
58
|
+
hotkey_ss58: str,
|
59
|
+
hotkey_display: str,
|
60
|
+
prompt: bool = False,
|
61
|
+
):
|
62
|
+
"""Associates a hotkey with a wallet"""
|
63
|
+
|
64
|
+
owner_ss58 = await subtensor.get_hotkey_owner(hotkey_ss58)
|
65
|
+
if owner_ss58:
|
66
|
+
if owner_ss58 == wallet.coldkeypub.ss58_address:
|
67
|
+
console.print(
|
68
|
+
f":white_heavy_check_mark: {hotkey_display.capitalize()} is already "
|
69
|
+
f"associated with \nwallet [blue]{wallet.name}[/blue], "
|
70
|
+
f"SS58: [{COLORS.GENERAL.CK}]{owner_ss58}[/{COLORS.GENERAL.CK}]"
|
71
|
+
)
|
72
|
+
return True
|
73
|
+
else:
|
74
|
+
owner_wallet = _get_wallet_by_ss58(wallet.path, owner_ss58)
|
75
|
+
wallet_name = owner_wallet.name if owner_wallet else "unknown wallet"
|
76
|
+
console.print(
|
77
|
+
f"[yellow]Warning[/yellow]: {hotkey_display.capitalize()} is already associated with \n"
|
78
|
+
f"wallet: [blue]{wallet_name}[/blue], SS58: [{COLORS.GENERAL.CK}]{owner_ss58}[/{COLORS.GENERAL.CK}]"
|
79
|
+
)
|
80
|
+
return False
|
81
|
+
else:
|
82
|
+
console.print(
|
83
|
+
f"{hotkey_display.capitalize()} is not associated with any wallet"
|
84
|
+
)
|
85
|
+
|
86
|
+
if prompt and not Confirm.ask("Do you want to continue with the association?"):
|
87
|
+
return False
|
88
|
+
|
89
|
+
if not unlock_key(wallet).success:
|
90
|
+
return False
|
91
|
+
|
92
|
+
call = await subtensor.substrate.compose_call(
|
93
|
+
call_module="SubtensorModule",
|
94
|
+
call_function="try_associate_hotkey",
|
95
|
+
call_params={
|
96
|
+
"hotkey": hotkey_ss58,
|
97
|
+
},
|
98
|
+
)
|
99
|
+
|
100
|
+
with console.status(":satellite: Associating hotkey on-chain..."):
|
101
|
+
success, err_msg = await subtensor.sign_and_send_extrinsic(
|
102
|
+
call,
|
103
|
+
wallet,
|
104
|
+
wait_for_inclusion=True,
|
105
|
+
wait_for_finalization=False,
|
106
|
+
)
|
107
|
+
|
108
|
+
if not success:
|
109
|
+
console.print(
|
110
|
+
f"[red]:cross_mark: Failed to associate hotkey: {err_msg}[/red]"
|
111
|
+
)
|
112
|
+
return False
|
113
|
+
|
114
|
+
console.print(
|
115
|
+
f":white_heavy_check_mark: Successfully associated {hotkey_display} with \n"
|
116
|
+
f"wallet [blue]{wallet.name}[/blue], "
|
117
|
+
f"SS58: [{COLORS.GENERAL.CK}]{wallet.coldkeypub.ss58_address}[/{COLORS.GENERAL.CK}]"
|
118
|
+
)
|
119
|
+
return True
|
120
|
+
|
121
|
+
|
50
122
|
async def regen_coldkey(
|
51
123
|
wallet: Wallet,
|
52
124
|
mnemonic: Optional[str],
|
@@ -55,6 +127,7 @@ async def regen_coldkey(
|
|
55
127
|
json_password: Optional[str] = "",
|
56
128
|
use_password: Optional[bool] = True,
|
57
129
|
overwrite: Optional[bool] = False,
|
130
|
+
json_output: bool = False,
|
58
131
|
):
|
59
132
|
"""Creates a new coldkey under this wallet"""
|
60
133
|
json_str: Optional[str] = None
|
@@ -71,16 +144,41 @@ async def regen_coldkey(
|
|
71
144
|
use_password=use_password,
|
72
145
|
overwrite=overwrite,
|
73
146
|
)
|
74
|
-
|
75
147
|
if isinstance(new_wallet, Wallet):
|
76
148
|
console.print(
|
77
149
|
"\n✅ [dark_sea_green]Regenerated coldkey successfully!\n",
|
78
|
-
f"[dark_sea_green]Wallet name: ({new_wallet.name}),
|
150
|
+
f"[dark_sea_green]Wallet name: ({new_wallet.name}), "
|
151
|
+
f"path: ({new_wallet.path}), "
|
152
|
+
f"coldkey ss58: ({new_wallet.coldkeypub.ss58_address})",
|
79
153
|
)
|
154
|
+
if json_output:
|
155
|
+
json_console.print(
|
156
|
+
json.dumps(
|
157
|
+
{
|
158
|
+
"success": True,
|
159
|
+
"data": {
|
160
|
+
"name": new_wallet.name,
|
161
|
+
"path": new_wallet.path,
|
162
|
+
"hotkey": new_wallet.hotkey_str,
|
163
|
+
"hotkey_ss58": new_wallet.hotkey.ss58_address,
|
164
|
+
"coldkey_ss58": new_wallet.coldkeypub.ss58_address,
|
165
|
+
},
|
166
|
+
"error": "",
|
167
|
+
}
|
168
|
+
)
|
169
|
+
)
|
80
170
|
except ValueError:
|
81
171
|
print_error("Mnemonic phrase is invalid")
|
172
|
+
if json_output:
|
173
|
+
json_console.print(
|
174
|
+
'{"success": false, "error": "Mnemonic phrase is invalid", "data": null}'
|
175
|
+
)
|
82
176
|
except KeyFileError:
|
83
177
|
print_error("KeyFileError: File is not writable")
|
178
|
+
if json_output:
|
179
|
+
json_console.print(
|
180
|
+
'{"success": false, "error": "Keyfile is not writable", "data": null}'
|
181
|
+
)
|
84
182
|
|
85
183
|
|
86
184
|
async def regen_coldkey_pub(
|
@@ -88,6 +186,7 @@ async def regen_coldkey_pub(
|
|
88
186
|
ss58_address: str,
|
89
187
|
public_key_hex: str,
|
90
188
|
overwrite: Optional[bool] = False,
|
189
|
+
json_output: bool = False,
|
91
190
|
):
|
92
191
|
"""Creates a new coldkeypub under this wallet."""
|
93
192
|
try:
|
@@ -99,10 +198,31 @@ async def regen_coldkey_pub(
|
|
99
198
|
if isinstance(new_coldkeypub, Wallet):
|
100
199
|
console.print(
|
101
200
|
"\n✅ [dark_sea_green]Regenerated coldkeypub successfully!\n",
|
102
|
-
f"[dark_sea_green]Wallet name: ({new_coldkeypub.name}), path: ({new_coldkeypub.path}),
|
201
|
+
f"[dark_sea_green]Wallet name: ({new_coldkeypub.name}), path: ({new_coldkeypub.path}), "
|
202
|
+
f"coldkey ss58: ({new_coldkeypub.coldkeypub.ss58_address})",
|
103
203
|
)
|
204
|
+
if json_output:
|
205
|
+
json_console.print(
|
206
|
+
json.dumps(
|
207
|
+
{
|
208
|
+
"success": True,
|
209
|
+
"data": {
|
210
|
+
"name": new_coldkeypub.name,
|
211
|
+
"path": new_coldkeypub.path,
|
212
|
+
"hotkey": new_coldkeypub.hotkey_str,
|
213
|
+
"hotkey_ss58": new_coldkeypub.hotkey.ss58_address,
|
214
|
+
"coldkey_ss58": new_coldkeypub.coldkeypub.ss58_address,
|
215
|
+
},
|
216
|
+
"error": "",
|
217
|
+
}
|
218
|
+
)
|
219
|
+
)
|
104
220
|
except KeyFileError:
|
105
221
|
print_error("KeyFileError: File is not writable")
|
222
|
+
if json_output:
|
223
|
+
json_console.print(
|
224
|
+
'{"success": false, "error": "Keyfile is not writable", "data": null}'
|
225
|
+
)
|
106
226
|
|
107
227
|
|
108
228
|
async def regen_hotkey(
|
@@ -113,6 +233,7 @@ async def regen_hotkey(
|
|
113
233
|
json_password: Optional[str] = "",
|
114
234
|
use_password: Optional[bool] = False,
|
115
235
|
overwrite: Optional[bool] = False,
|
236
|
+
json_output: bool = False,
|
116
237
|
):
|
117
238
|
"""Creates a new hotkey under this wallet."""
|
118
239
|
json_str: Optional[str] = None
|
@@ -134,13 +255,37 @@ async def regen_hotkey(
|
|
134
255
|
if isinstance(new_hotkey_, Wallet):
|
135
256
|
console.print(
|
136
257
|
"\n✅ [dark_sea_green]Regenerated hotkey successfully!\n",
|
137
|
-
f"[dark_sea_green]Wallet name: "
|
138
|
-
f"
|
258
|
+
f"[dark_sea_green]Wallet name: ({new_hotkey_.name}), path: ({new_hotkey_.path}), "
|
259
|
+
f"hotkey ss58: ({new_hotkey_.hotkey.ss58_address})",
|
139
260
|
)
|
261
|
+
if json_output:
|
262
|
+
json_console.print(
|
263
|
+
json.dumps(
|
264
|
+
{
|
265
|
+
"success": True,
|
266
|
+
"data": {
|
267
|
+
"name": new_hotkey_.name,
|
268
|
+
"path": new_hotkey_.path,
|
269
|
+
"hotkey": new_hotkey_.hotkey_str,
|
270
|
+
"hotkey_ss58": new_hotkey_.hotkey.ss58_address,
|
271
|
+
"coldkey_ss58": new_hotkey_.coldkeypub.ss58_address,
|
272
|
+
},
|
273
|
+
"error": "",
|
274
|
+
}
|
275
|
+
)
|
276
|
+
)
|
140
277
|
except ValueError:
|
141
278
|
print_error("Mnemonic phrase is invalid")
|
279
|
+
if json_output:
|
280
|
+
json_console.print(
|
281
|
+
'{"success": false, "error": "Mnemonic phrase is invalid", "data": null}'
|
282
|
+
)
|
142
283
|
except KeyFileError:
|
143
284
|
print_error("KeyFileError: File is not writable")
|
285
|
+
if json_output:
|
286
|
+
json_console.print(
|
287
|
+
'{"success": false, "error": "Keyfile is not writable", "data": null}'
|
288
|
+
)
|
144
289
|
|
145
290
|
|
146
291
|
async def new_hotkey(
|
@@ -149,6 +294,7 @@ async def new_hotkey(
|
|
149
294
|
use_password: bool,
|
150
295
|
uri: Optional[str] = None,
|
151
296
|
overwrite: Optional[bool] = False,
|
297
|
+
json_output: bool = False,
|
152
298
|
):
|
153
299
|
"""Creates a new hotkey under this wallet."""
|
154
300
|
try:
|
@@ -157,6 +303,7 @@ async def new_hotkey(
|
|
157
303
|
keypair = Keypair.create_from_uri(uri)
|
158
304
|
except Exception as e:
|
159
305
|
print_error(f"Failed to create keypair from URI {uri}: {str(e)}")
|
306
|
+
return
|
160
307
|
wallet.set_hotkey(keypair=keypair, encrypt=use_password)
|
161
308
|
console.print(
|
162
309
|
f"[dark_sea_green]Hotkey created from URI: {uri}[/dark_sea_green]"
|
@@ -168,8 +315,28 @@ async def new_hotkey(
|
|
168
315
|
overwrite=overwrite,
|
169
316
|
)
|
170
317
|
console.print("[dark_sea_green]Hotkey created[/dark_sea_green]")
|
318
|
+
if json_output:
|
319
|
+
json_console.print(
|
320
|
+
json.dumps(
|
321
|
+
{
|
322
|
+
"success": True,
|
323
|
+
"data": {
|
324
|
+
"name": wallet.name,
|
325
|
+
"path": wallet.path,
|
326
|
+
"hotkey": wallet.hotkey_str,
|
327
|
+
"hotkey_ss58": wallet.hotkey.ss58_address,
|
328
|
+
"coldkey_ss58": wallet.coldkeypub.ss58_address,
|
329
|
+
},
|
330
|
+
"error": "",
|
331
|
+
}
|
332
|
+
)
|
333
|
+
)
|
171
334
|
except KeyFileError:
|
172
335
|
print_error("KeyFileError: File is not writable")
|
336
|
+
if json_output:
|
337
|
+
json_console.print(
|
338
|
+
'{"success": false, "error": "Keyfile is not writable", "data": null}'
|
339
|
+
)
|
173
340
|
|
174
341
|
|
175
342
|
async def new_coldkey(
|
@@ -178,6 +345,7 @@ async def new_coldkey(
|
|
178
345
|
use_password: bool,
|
179
346
|
uri: Optional[str] = None,
|
180
347
|
overwrite: Optional[bool] = False,
|
348
|
+
json_output: bool = False,
|
181
349
|
):
|
182
350
|
"""Creates a new coldkey under this wallet."""
|
183
351
|
try:
|
@@ -198,8 +366,32 @@ async def new_coldkey(
|
|
198
366
|
overwrite=overwrite,
|
199
367
|
)
|
200
368
|
console.print("[dark_sea_green]Coldkey created[/dark_sea_green]")
|
201
|
-
|
369
|
+
if json_output:
|
370
|
+
json_console.print(
|
371
|
+
json.dumps(
|
372
|
+
{
|
373
|
+
"success": True,
|
374
|
+
"data": {
|
375
|
+
"name": wallet.name,
|
376
|
+
"path": wallet.path,
|
377
|
+
"coldkey_ss58": wallet.coldkeypub.ss58_address,
|
378
|
+
},
|
379
|
+
"error": "",
|
380
|
+
}
|
381
|
+
)
|
382
|
+
)
|
383
|
+
except KeyFileError as e:
|
202
384
|
print_error("KeyFileError: File is not writable")
|
385
|
+
if json_output:
|
386
|
+
json_console.print(
|
387
|
+
json.dumps(
|
388
|
+
{
|
389
|
+
"success": False,
|
390
|
+
"error": f"Keyfile is not writable: {e}",
|
391
|
+
"data": None,
|
392
|
+
}
|
393
|
+
)
|
394
|
+
)
|
203
395
|
|
204
396
|
|
205
397
|
async def wallet_create(
|
@@ -208,16 +400,28 @@ async def wallet_create(
|
|
208
400
|
use_password: bool = True,
|
209
401
|
uri: Optional[str] = None,
|
210
402
|
overwrite: Optional[bool] = False,
|
403
|
+
json_output: bool = False,
|
211
404
|
):
|
212
405
|
"""Creates a new wallet."""
|
406
|
+
output_dict = {"success": False, "error": "", "data": None}
|
213
407
|
if uri:
|
214
408
|
try:
|
215
409
|
keypair = Keypair.create_from_uri(uri)
|
216
410
|
wallet.set_coldkey(keypair=keypair, encrypt=False, overwrite=False)
|
217
411
|
wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=False)
|
218
412
|
wallet.set_hotkey(keypair=keypair, encrypt=False, overwrite=False)
|
413
|
+
output_dict["success"] = True
|
414
|
+
output_dict["data"] = {
|
415
|
+
"name": wallet.name,
|
416
|
+
"path": wallet.path,
|
417
|
+
"hotkey": wallet.hotkey_str,
|
418
|
+
"hotkey_ss58": wallet.hotkey.ss58_address,
|
419
|
+
"coldkey_ss58": wallet.coldkeypub.ss58_address,
|
420
|
+
}
|
219
421
|
except Exception as e:
|
220
|
-
|
422
|
+
err = f"Failed to create keypair from URI: {str(e)}"
|
423
|
+
print_error(err)
|
424
|
+
output_dict["error"] = err
|
221
425
|
console.print(
|
222
426
|
f"[dark_sea_green]Wallet created from URI: {uri}[/dark_sea_green]"
|
223
427
|
)
|
@@ -229,9 +433,18 @@ async def wallet_create(
|
|
229
433
|
overwrite=overwrite,
|
230
434
|
)
|
231
435
|
console.print("[dark_sea_green]Coldkey created[/dark_sea_green]")
|
436
|
+
output_dict["success"] = True
|
437
|
+
output_dict["data"] = {
|
438
|
+
"name": wallet.name,
|
439
|
+
"path": wallet.path,
|
440
|
+
"hotkey": wallet.hotkey_str,
|
441
|
+
"hotkey_ss58": wallet.hotkey.ss58_address,
|
442
|
+
"coldkey_ss58": wallet.coldkeypub.ss58_address,
|
443
|
+
}
|
232
444
|
except KeyFileError:
|
233
|
-
|
234
|
-
|
445
|
+
err = "KeyFileError: File is not writable"
|
446
|
+
print_error(err)
|
447
|
+
output_dict["error"] = err
|
235
448
|
try:
|
236
449
|
wallet.create_new_hotkey(
|
237
450
|
n_words=n_words,
|
@@ -239,8 +452,20 @@ async def wallet_create(
|
|
239
452
|
overwrite=overwrite,
|
240
453
|
)
|
241
454
|
console.print("[dark_sea_green]Hotkey created[/dark_sea_green]")
|
455
|
+
output_dict["success"] = True
|
456
|
+
output_dict["data"] = {
|
457
|
+
"name": wallet.name,
|
458
|
+
"path": wallet.path,
|
459
|
+
"hotkey": wallet.hotkey_str,
|
460
|
+
"hotkey_ss58": wallet.hotkey.ss58_address,
|
461
|
+
"coldkey_ss58": wallet.coldkeypub.ss58_address,
|
462
|
+
}
|
242
463
|
except KeyFileError:
|
243
|
-
|
464
|
+
err = "KeyFileError: File is not writable"
|
465
|
+
print_error(err)
|
466
|
+
output_dict["error"] = err
|
467
|
+
if json_output:
|
468
|
+
json_console.print(json.dumps(output_dict))
|
244
469
|
|
245
470
|
|
246
471
|
def get_coldkey_wallets_for_path(path: str) -> list[Wallet]:
|
@@ -254,6 +479,15 @@ def get_coldkey_wallets_for_path(path: str) -> list[Wallet]:
|
|
254
479
|
return wallets
|
255
480
|
|
256
481
|
|
482
|
+
def _get_wallet_by_ss58(path: str, ss58_address: str) -> Optional[Wallet]:
|
483
|
+
"""Find a wallet by its SS58 address in the given path."""
|
484
|
+
ss58_addresses, wallet_names = _get_coldkey_ss58_addresses_for_path(path)
|
485
|
+
for wallet_name, addr in zip(wallet_names, ss58_addresses):
|
486
|
+
if addr == ss58_address:
|
487
|
+
return Wallet(path=path, name=wallet_name)
|
488
|
+
return None
|
489
|
+
|
490
|
+
|
257
491
|
def _get_coldkey_ss58_addresses_for_path(path: str) -> tuple[list[str], list[str]]:
|
258
492
|
"""Get all coldkey ss58 addresses from path."""
|
259
493
|
|
@@ -280,6 +514,7 @@ async def wallet_balance(
|
|
280
514
|
subtensor: SubtensorInterface,
|
281
515
|
all_balances: bool,
|
282
516
|
ss58_addresses: Optional[str] = None,
|
517
|
+
json_output: bool = False,
|
283
518
|
):
|
284
519
|
"""Retrieves the current balance of the specified wallet"""
|
285
520
|
if ss58_addresses:
|
@@ -395,6 +630,31 @@ async def wallet_balance(
|
|
395
630
|
)
|
396
631
|
console.print(Padding(table, (0, 0, 0, 4)))
|
397
632
|
await subtensor.substrate.close()
|
633
|
+
if json_output:
|
634
|
+
output_balances = {
|
635
|
+
key: {
|
636
|
+
"coldkey": value[0],
|
637
|
+
"free": value[1].tao,
|
638
|
+
"staked": value[2].tao,
|
639
|
+
"staked_with_slippage": value[3].tao,
|
640
|
+
"total": (value[1] + value[2]).tao,
|
641
|
+
"total_with_slippage": (value[1] + value[3]).tao,
|
642
|
+
}
|
643
|
+
for (key, value) in balances.items()
|
644
|
+
}
|
645
|
+
output_dict = {
|
646
|
+
"balances": output_balances,
|
647
|
+
"totals": {
|
648
|
+
"free": total_free_balance.tao,
|
649
|
+
"staked": total_staked_balance.tao,
|
650
|
+
"staked_with_slippage": total_staked_with_slippage.tao,
|
651
|
+
"total": (total_free_balance + total_staked_balance).tao,
|
652
|
+
"total_with_slippage": (
|
653
|
+
total_free_balance + total_staked_with_slippage
|
654
|
+
).tao,
|
655
|
+
},
|
656
|
+
}
|
657
|
+
json_console.print(json.dumps(output_dict))
|
398
658
|
return total_free_balance
|
399
659
|
|
400
660
|
|
@@ -526,7 +786,7 @@ async def wallet_history(wallet: Wallet):
|
|
526
786
|
console.print(table)
|
527
787
|
|
528
788
|
|
529
|
-
async def wallet_list(wallet_path: str):
|
789
|
+
async def wallet_list(wallet_path: str, json_output: bool):
|
530
790
|
"""Lists wallets."""
|
531
791
|
wallets = utils.get_coldkey_wallets_for_path(wallet_path)
|
532
792
|
print_verbose(f"Using wallets path: {wallet_path}")
|
@@ -534,6 +794,7 @@ async def wallet_list(wallet_path: str):
|
|
534
794
|
err_console.print(f"[red]No wallets found in dir: {wallet_path}[/red]")
|
535
795
|
|
536
796
|
root = Tree("Wallets")
|
797
|
+
main_data_dict = {"wallets": []}
|
537
798
|
for wallet in wallets:
|
538
799
|
if (
|
539
800
|
wallet.coldkeypub_file.exists_on_device()
|
@@ -546,23 +807,39 @@ async def wallet_list(wallet_path: str):
|
|
546
807
|
wallet_tree = root.add(
|
547
808
|
f"[bold blue]Coldkey[/bold blue] [green]{wallet.name}[/green] ss58_address [green]{coldkeypub_str}[/green]"
|
548
809
|
)
|
810
|
+
wallet_hotkeys = []
|
811
|
+
wallet_dict = {
|
812
|
+
"name": wallet.name,
|
813
|
+
"ss58_address": coldkeypub_str,
|
814
|
+
"hotkeys": wallet_hotkeys,
|
815
|
+
}
|
816
|
+
main_data_dict["wallets"].append(wallet_dict)
|
549
817
|
hotkeys = utils.get_hotkey_wallets_for_wallet(
|
550
818
|
wallet, show_nulls=True, show_encrypted=True
|
551
819
|
)
|
552
820
|
for hkey in hotkeys:
|
553
821
|
data = f"[bold red]Hotkey[/bold red][green] {hkey}[/green] (?)"
|
822
|
+
hk_data = {"name": hkey.name, "ss58_address": "?"}
|
554
823
|
if hkey:
|
555
824
|
try:
|
556
|
-
data =
|
825
|
+
data = (
|
826
|
+
f"[bold red]Hotkey[/bold red] [green]{hkey.hotkey_str}[/green] "
|
827
|
+
f"ss58_address [green]{hkey.hotkey.ss58_address}[/green]\n"
|
828
|
+
)
|
829
|
+
hk_data["name"] = hkey.hotkey_str
|
830
|
+
hk_data["ss58_address"] = hkey.hotkey.ss58_address
|
557
831
|
except UnicodeDecodeError:
|
558
832
|
pass
|
559
833
|
wallet_tree.add(data)
|
834
|
+
wallet_hotkeys.append(hk_data)
|
560
835
|
|
561
836
|
if not wallets:
|
562
837
|
print_verbose(f"No wallets found in path: {wallet_path}")
|
563
838
|
root.add("[bold red]No wallets found.")
|
564
|
-
|
565
|
-
|
839
|
+
if json_output:
|
840
|
+
json_console.print(json.dumps(main_data_dict))
|
841
|
+
else:
|
842
|
+
console.print(root)
|
566
843
|
|
567
844
|
|
568
845
|
async def _get_total_balance(
|
@@ -639,23 +916,33 @@ async def overview(
|
|
639
916
|
exclude_hotkeys: Optional[list[str]] = None,
|
640
917
|
netuids_filter: Optional[list[int]] = None,
|
641
918
|
verbose: bool = False,
|
919
|
+
json_output: bool = False,
|
642
920
|
):
|
643
921
|
"""Prints an overview for the wallet's coldkey."""
|
644
922
|
|
645
923
|
total_balance = Balance(0)
|
646
924
|
|
647
|
-
# We are printing for every coldkey.
|
648
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
649
|
-
all_hotkeys, total_balance = await _get_total_balance(
|
650
|
-
total_balance, subtensor, wallet, all_wallets, block_hash=block_hash
|
651
|
-
)
|
652
|
-
_dynamic_info = await subtensor.all_subnets()
|
653
|
-
dynamic_info = {info.netuid: info for info in _dynamic_info}
|
654
|
-
|
655
925
|
with console.status(
|
656
926
|
f":satellite: Synchronizing with chain [white]{subtensor.network}[/white]",
|
657
927
|
spinner="aesthetic",
|
658
928
|
) as status:
|
929
|
+
# We are printing for every coldkey.
|
930
|
+
block_hash = await subtensor.substrate.get_chain_head()
|
931
|
+
(
|
932
|
+
(all_hotkeys, total_balance),
|
933
|
+
_dynamic_info,
|
934
|
+
block,
|
935
|
+
all_netuids,
|
936
|
+
) = await asyncio.gather(
|
937
|
+
_get_total_balance(
|
938
|
+
total_balance, subtensor, wallet, all_wallets, block_hash=block_hash
|
939
|
+
),
|
940
|
+
subtensor.all_subnets(block_hash=block_hash),
|
941
|
+
subtensor.substrate.get_block_number(block_hash=block_hash),
|
942
|
+
subtensor.get_all_subnet_netuids(block_hash=block_hash),
|
943
|
+
)
|
944
|
+
dynamic_info = {info.netuid: info for info in _dynamic_info}
|
945
|
+
|
659
946
|
# We are printing for a select number of hotkeys from all_hotkeys.
|
660
947
|
if include_hotkeys or exclude_hotkeys:
|
661
948
|
all_hotkeys = _get_hotkeys(include_hotkeys, exclude_hotkeys, all_hotkeys)
|
@@ -667,10 +954,6 @@ async def overview(
|
|
667
954
|
|
668
955
|
# Pull neuron info for all keys.
|
669
956
|
neurons: dict[str, list[NeuronInfoLite]] = {}
|
670
|
-
block, all_netuids = await asyncio.gather(
|
671
|
-
subtensor.substrate.get_block_number(None),
|
672
|
-
subtensor.get_all_subnet_netuids(),
|
673
|
-
)
|
674
957
|
|
675
958
|
netuids = await subtensor.filter_netuids_by_registered_hotkeys(
|
676
959
|
all_netuids, netuids_filter, all_hotkeys, reuse_block=True
|
@@ -705,16 +988,27 @@ async def overview(
|
|
705
988
|
neurons = _process_neuron_results(results, neurons, netuids)
|
706
989
|
# Setup outer table.
|
707
990
|
grid = Table.grid(pad_edge=True)
|
991
|
+
data_dict = {
|
992
|
+
"wallet": "",
|
993
|
+
"network": subtensor.network,
|
994
|
+
"subnets": [],
|
995
|
+
"total_balance": 0.0,
|
996
|
+
}
|
708
997
|
|
709
998
|
# Add title
|
710
999
|
if not all_wallets:
|
711
1000
|
title = "[underline dark_orange]Wallet[/underline dark_orange]\n"
|
712
|
-
details =
|
1001
|
+
details = (
|
1002
|
+
f"[bright_cyan]{wallet.name}[/bright_cyan] : "
|
1003
|
+
f"[bright_magenta]{wallet.coldkeypub.ss58_address}[/bright_magenta]"
|
1004
|
+
)
|
713
1005
|
grid.add_row(Align(title, vertical="middle", align="center"))
|
714
1006
|
grid.add_row(Align(details, vertical="middle", align="center"))
|
1007
|
+
data_dict["wallet"] = f"{wallet.name}|{wallet.coldkeypub.ss58_address}"
|
715
1008
|
else:
|
716
1009
|
title = "[underline dark_orange]All Wallets:[/underline dark_orange]"
|
717
1010
|
grid.add_row(Align(title, vertical="middle", align="center"))
|
1011
|
+
data_dict["wallet"] = "All"
|
718
1012
|
|
719
1013
|
grid.add_row(
|
720
1014
|
Align(
|
@@ -732,6 +1026,14 @@ async def overview(
|
|
732
1026
|
)
|
733
1027
|
for netuid, subnet_tempo in zip(netuids, tempos):
|
734
1028
|
table_data = []
|
1029
|
+
subnet_dict = {
|
1030
|
+
"netuid": netuid,
|
1031
|
+
"tempo": subnet_tempo,
|
1032
|
+
"neurons": [],
|
1033
|
+
"name": "",
|
1034
|
+
"symbol": "",
|
1035
|
+
}
|
1036
|
+
data_dict["subnets"].append(subnet_dict)
|
735
1037
|
total_rank = 0.0
|
736
1038
|
total_trust = 0.0
|
737
1039
|
total_consensus = 0.0
|
@@ -786,6 +1088,26 @@ async def overview(
|
|
786
1088
|
),
|
787
1089
|
nn.hotkey[:10],
|
788
1090
|
]
|
1091
|
+
neuron_dict = {
|
1092
|
+
"coldkey": hotwallet.name,
|
1093
|
+
"hotkey": hotwallet.hotkey_str,
|
1094
|
+
"uid": uid,
|
1095
|
+
"active": active,
|
1096
|
+
"stake": stake,
|
1097
|
+
"rank": rank,
|
1098
|
+
"trust": trust,
|
1099
|
+
"consensus": consensus,
|
1100
|
+
"incentive": incentive,
|
1101
|
+
"dividends": dividends,
|
1102
|
+
"emission": emission,
|
1103
|
+
"validator_trust": validator_trust,
|
1104
|
+
"validator_permit": validator_permit,
|
1105
|
+
"last_update": last_update,
|
1106
|
+
"axon": int_to_ip(nn.axon_info.ip) + ":" + str(nn.axon_info.port)
|
1107
|
+
if nn.axon_info.port != 0
|
1108
|
+
else None,
|
1109
|
+
"hotkey_ss58": nn.hotkey,
|
1110
|
+
}
|
789
1111
|
|
790
1112
|
total_rank += rank
|
791
1113
|
total_trust += trust
|
@@ -798,11 +1120,16 @@ async def overview(
|
|
798
1120
|
total_neurons += 1
|
799
1121
|
|
800
1122
|
table_data.append(row)
|
1123
|
+
subnet_dict["neurons"].append(neuron_dict)
|
801
1124
|
|
802
1125
|
# Add subnet header
|
1126
|
+
sn_name = get_subnet_name(dynamic_info[netuid])
|
1127
|
+
sn_symbol = dynamic_info[netuid].symbol
|
803
1128
|
grid.add_row(
|
804
|
-
f"Subnet: [dark_orange]{netuid}: {
|
1129
|
+
f"Subnet: [dark_orange]{netuid}: {sn_name} {sn_symbol}[/dark_orange]"
|
805
1130
|
)
|
1131
|
+
subnet_dict["name"] = sn_name
|
1132
|
+
subnet_dict["symbol"] = sn_symbol
|
806
1133
|
width = console.width
|
807
1134
|
table = Table(
|
808
1135
|
show_footer=False,
|
@@ -937,6 +1264,7 @@ async def overview(
|
|
937
1264
|
caption = "\n[italic][dim][bright_cyan]Wallet balance: [dark_orange]\u03c4" + str(
|
938
1265
|
total_balance.tao
|
939
1266
|
)
|
1267
|
+
data_dict["total_balance"] = total_balance.tao
|
940
1268
|
grid.add_row(Align(caption, vertical="middle", align="center"))
|
941
1269
|
|
942
1270
|
if console.width < 150:
|
@@ -944,7 +1272,10 @@ async def overview(
|
|
944
1272
|
"[yellow]Warning: Your terminal width might be too small to view all information clearly"
|
945
1273
|
)
|
946
1274
|
# Print the entire table/grid
|
947
|
-
|
1275
|
+
if not json_output:
|
1276
|
+
console.print(grid, width=None)
|
1277
|
+
else:
|
1278
|
+
json_console.print(json.dumps(data_dict))
|
948
1279
|
|
949
1280
|
|
950
1281
|
def _get_hotkeys(
|
@@ -1109,17 +1440,23 @@ async def transfer(
|
|
1109
1440
|
destination: str,
|
1110
1441
|
amount: float,
|
1111
1442
|
transfer_all: bool,
|
1443
|
+
era: int,
|
1112
1444
|
prompt: bool,
|
1445
|
+
json_output: bool,
|
1113
1446
|
):
|
1114
1447
|
"""Transfer token of amount to destination."""
|
1115
|
-
await transfer_extrinsic(
|
1448
|
+
result = await transfer_extrinsic(
|
1116
1449
|
subtensor=subtensor,
|
1117
1450
|
wallet=wallet,
|
1118
1451
|
destination=destination,
|
1119
1452
|
amount=Balance.from_tao(amount),
|
1120
1453
|
transfer_all=transfer_all,
|
1454
|
+
era=era,
|
1121
1455
|
prompt=prompt,
|
1122
1456
|
)
|
1457
|
+
if json_output:
|
1458
|
+
json_console.print(json.dumps({"success": result}))
|
1459
|
+
return result
|
1123
1460
|
|
1124
1461
|
|
1125
1462
|
async def inspect(
|
@@ -1128,6 +1465,7 @@ async def inspect(
|
|
1128
1465
|
netuids_filter: list[int],
|
1129
1466
|
all_wallets: bool = False,
|
1130
1467
|
):
|
1468
|
+
# TODO add json_output when this is re-enabled and updated for dTAO
|
1131
1469
|
def delegate_row_maker(
|
1132
1470
|
delegates_: list[tuple[DelegateInfo, Balance]],
|
1133
1471
|
) -> Generator[list[str], None, None]:
|
@@ -1303,14 +1641,18 @@ async def swap_hotkey(
|
|
1303
1641
|
new_wallet: Wallet,
|
1304
1642
|
subtensor: SubtensorInterface,
|
1305
1643
|
prompt: bool,
|
1644
|
+
json_output: bool,
|
1306
1645
|
):
|
1307
1646
|
"""Swap your hotkey for all registered axons on the network."""
|
1308
|
-
|
1647
|
+
result = await swap_hotkey_extrinsic(
|
1309
1648
|
subtensor,
|
1310
1649
|
original_wallet,
|
1311
1650
|
new_wallet,
|
1312
1651
|
prompt=prompt,
|
1313
1652
|
)
|
1653
|
+
if json_output:
|
1654
|
+
json_console.print(json.dumps({"success": result}))
|
1655
|
+
return result
|
1314
1656
|
|
1315
1657
|
|
1316
1658
|
def create_identity_table(title: str = None):
|
@@ -1349,9 +1691,10 @@ async def set_id(
|
|
1349
1691
|
additional: str,
|
1350
1692
|
github_repo: str,
|
1351
1693
|
prompt: bool,
|
1694
|
+
json_output: bool = False,
|
1352
1695
|
):
|
1353
1696
|
"""Create a new or update existing identity on-chain."""
|
1354
|
-
|
1697
|
+
output_dict = {"success": False, "identity": None, "error": ""}
|
1355
1698
|
identity_data = {
|
1356
1699
|
"name": name.encode(),
|
1357
1700
|
"url": web_url.encode(),
|
@@ -1378,20 +1721,31 @@ async def set_id(
|
|
1378
1721
|
|
1379
1722
|
if not success:
|
1380
1723
|
err_console.print(f"[red]:cross_mark: Failed![/red] {err_msg}")
|
1724
|
+
output_dict["error"] = err_msg
|
1725
|
+
if json_output:
|
1726
|
+
json_console.print(json.dumps(output_dict))
|
1381
1727
|
return
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1728
|
+
else:
|
1729
|
+
console.print(":white_heavy_check_mark: [dark_sea_green3]Success!")
|
1730
|
+
output_dict["success"] = True
|
1731
|
+
identity = await subtensor.query_identity(wallet.coldkeypub.ss58_address)
|
1385
1732
|
|
1386
1733
|
table = create_identity_table(title="New on-chain Identity")
|
1387
1734
|
table.add_row("Address", wallet.coldkeypub.ss58_address)
|
1388
1735
|
for key, value in identity.items():
|
1389
1736
|
table.add_row(key, str(value) if value else "~")
|
1390
|
-
|
1391
|
-
|
1737
|
+
output_dict["identity"] = identity
|
1738
|
+
console.print(table)
|
1739
|
+
if json_output:
|
1740
|
+
json_console.print(json.dumps(output_dict))
|
1392
1741
|
|
1393
1742
|
|
1394
|
-
async def get_id(
|
1743
|
+
async def get_id(
|
1744
|
+
subtensor: SubtensorInterface,
|
1745
|
+
ss58_address: str,
|
1746
|
+
title: str = None,
|
1747
|
+
json_output: bool = False,
|
1748
|
+
):
|
1395
1749
|
with console.status(
|
1396
1750
|
":satellite: [bold green]Querying chain identity...", spinner="earth"
|
1397
1751
|
):
|
@@ -1403,6 +1757,8 @@ async def get_id(subtensor: SubtensorInterface, ss58_address: str, title: str =
|
|
1403
1757
|
f" for [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]"
|
1404
1758
|
f" on {subtensor}"
|
1405
1759
|
)
|
1760
|
+
if json_output:
|
1761
|
+
json_console.print("{}")
|
1406
1762
|
return {}
|
1407
1763
|
|
1408
1764
|
table = create_identity_table(title)
|
@@ -1411,6 +1767,8 @@ async def get_id(subtensor: SubtensorInterface, ss58_address: str, title: str =
|
|
1411
1767
|
table.add_row(key, str(value) if value else "~")
|
1412
1768
|
|
1413
1769
|
console.print(table)
|
1770
|
+
if json_output:
|
1771
|
+
json_console.print(json.dumps(identity))
|
1414
1772
|
return identity
|
1415
1773
|
|
1416
1774
|
|
@@ -1451,7 +1809,9 @@ async def check_coldkey_swap(wallet: Wallet, subtensor: SubtensorInterface):
|
|
1451
1809
|
)
|
1452
1810
|
|
1453
1811
|
|
1454
|
-
async def sign(
|
1812
|
+
async def sign(
|
1813
|
+
wallet: Wallet, message: str, use_hotkey: str, json_output: bool = False
|
1814
|
+
):
|
1455
1815
|
"""Sign a message using the provided wallet or hotkey."""
|
1456
1816
|
|
1457
1817
|
if not use_hotkey:
|
@@ -1471,4 +1831,260 @@ async def sign(wallet: Wallet, message: str, use_hotkey: str):
|
|
1471
1831
|
|
1472
1832
|
signed_message = keypair.sign(message.encode("utf-8")).hex()
|
1473
1833
|
console.print("[dark_sea_green3]Message signed successfully:")
|
1834
|
+
if json_output:
|
1835
|
+
json_console.print(json.dumps({"signed_message": signed_message}))
|
1474
1836
|
console.print(signed_message)
|
1837
|
+
|
1838
|
+
|
1839
|
+
async def schedule_coldkey_swap(
|
1840
|
+
wallet: Wallet,
|
1841
|
+
subtensor: SubtensorInterface,
|
1842
|
+
new_coldkey_ss58: str,
|
1843
|
+
force_swap: bool = False,
|
1844
|
+
) -> bool:
|
1845
|
+
"""Schedules a coldkey swap operation to be executed at a future block.
|
1846
|
+
|
1847
|
+
Args:
|
1848
|
+
wallet (Wallet): The wallet initiating the coldkey swap
|
1849
|
+
subtensor (SubtensorInterface): Connection to the Bittensor network
|
1850
|
+
new_coldkey_ss58 (str): SS58 address of the new coldkey
|
1851
|
+
force_swap (bool, optional): Whether to force the swap even if the new coldkey is already scheduled for a swap. Defaults to False.
|
1852
|
+
Returns:
|
1853
|
+
bool: True if the swap was scheduled successfully, False otherwise
|
1854
|
+
"""
|
1855
|
+
if not is_valid_ss58_address(new_coldkey_ss58):
|
1856
|
+
print_error(f"Invalid SS58 address format: {new_coldkey_ss58}")
|
1857
|
+
return False
|
1858
|
+
|
1859
|
+
scheduled_coldkey_swap = await subtensor.get_scheduled_coldkey_swap()
|
1860
|
+
if wallet.coldkeypub.ss58_address in scheduled_coldkey_swap:
|
1861
|
+
print_error(
|
1862
|
+
f"Coldkey {wallet.coldkeypub.ss58_address} is already scheduled for a swap."
|
1863
|
+
)
|
1864
|
+
console.print("[dim]Use the force_swap (--force) flag to override this.[/dim]")
|
1865
|
+
if not force_swap:
|
1866
|
+
return False
|
1867
|
+
else:
|
1868
|
+
console.print(
|
1869
|
+
"[yellow]Continuing with the swap due to force_swap flag.[/yellow]\n"
|
1870
|
+
)
|
1871
|
+
|
1872
|
+
prompt = (
|
1873
|
+
"You are [red]swapping[/red] your [blue]coldkey[/blue] to a new address.\n"
|
1874
|
+
f"Current ss58: [{COLORS.G.CK}]{wallet.coldkeypub.ss58_address}[/{COLORS.G.CK}]\n"
|
1875
|
+
f"New ss58: [{COLORS.G.CK}]{new_coldkey_ss58}[/{COLORS.G.CK}]\n"
|
1876
|
+
"Are you sure you want to continue?"
|
1877
|
+
)
|
1878
|
+
if not Confirm.ask(prompt):
|
1879
|
+
return False
|
1880
|
+
|
1881
|
+
if not unlock_key(wallet).success:
|
1882
|
+
return False
|
1883
|
+
|
1884
|
+
block_pre_call, call = await asyncio.gather(
|
1885
|
+
subtensor.substrate.get_block_number(),
|
1886
|
+
subtensor.substrate.compose_call(
|
1887
|
+
call_module="SubtensorModule",
|
1888
|
+
call_function="schedule_swap_coldkey",
|
1889
|
+
call_params={
|
1890
|
+
"new_coldkey": new_coldkey_ss58,
|
1891
|
+
},
|
1892
|
+
),
|
1893
|
+
)
|
1894
|
+
|
1895
|
+
with console.status(":satellite: Scheduling coldkey swap on-chain..."):
|
1896
|
+
success, err_msg = await subtensor.sign_and_send_extrinsic(
|
1897
|
+
call,
|
1898
|
+
wallet,
|
1899
|
+
wait_for_inclusion=True,
|
1900
|
+
wait_for_finalization=True,
|
1901
|
+
)
|
1902
|
+
block_post_call = await subtensor.substrate.get_block_number()
|
1903
|
+
|
1904
|
+
if not success:
|
1905
|
+
print_error(f"Failed to schedule coldkey swap: {err_msg}")
|
1906
|
+
return False
|
1907
|
+
|
1908
|
+
console.print(
|
1909
|
+
":white_heavy_check_mark: [green]Successfully scheduled coldkey swap"
|
1910
|
+
)
|
1911
|
+
|
1912
|
+
swap_info = await find_coldkey_swap_extrinsic(
|
1913
|
+
subtensor=subtensor,
|
1914
|
+
start_block=block_pre_call,
|
1915
|
+
end_block=block_post_call,
|
1916
|
+
wallet_ss58=wallet.coldkeypub.ss58_address,
|
1917
|
+
)
|
1918
|
+
|
1919
|
+
if not swap_info:
|
1920
|
+
console.print(
|
1921
|
+
"[yellow]Warning: Could not find the swap extrinsic in recent blocks"
|
1922
|
+
)
|
1923
|
+
return True
|
1924
|
+
|
1925
|
+
console.print(
|
1926
|
+
"\n[green]Coldkey swap details:[/green]"
|
1927
|
+
f"\nBlock number: {swap_info['block_num']}"
|
1928
|
+
f"\nOriginal address: [{COLORS.G.CK}]{wallet.coldkeypub.ss58_address}[/{COLORS.G.CK}]"
|
1929
|
+
f"\nDestination address: [{COLORS.G.CK}]{swap_info['dest_coldkey']}[/{COLORS.G.CK}]"
|
1930
|
+
f"\nThe swap will be completed at block: [green]{swap_info['execution_block']}[/green]"
|
1931
|
+
f"\n[dim]You can provide the block number to `btcli wallet swap-check`[/dim]"
|
1932
|
+
)
|
1933
|
+
|
1934
|
+
|
1935
|
+
async def find_coldkey_swap_extrinsic(
|
1936
|
+
subtensor: SubtensorInterface,
|
1937
|
+
start_block: int,
|
1938
|
+
end_block: int,
|
1939
|
+
wallet_ss58: str,
|
1940
|
+
) -> dict:
|
1941
|
+
"""Search for a coldkey swap event in a range of blocks.
|
1942
|
+
|
1943
|
+
Args:
|
1944
|
+
subtensor: SubtensorInterface for chain queries
|
1945
|
+
start_block: Starting block number to search
|
1946
|
+
end_block: Ending block number to search (inclusive)
|
1947
|
+
wallet_ss58: SS58 address of the signing wallet
|
1948
|
+
|
1949
|
+
Returns:
|
1950
|
+
dict: Contains the following keys if found:
|
1951
|
+
- block_num: Block number where swap was scheduled
|
1952
|
+
- dest_coldkey: SS58 address of destination coldkey
|
1953
|
+
- execution_block: Block number when swap will execute
|
1954
|
+
Empty dict if not found
|
1955
|
+
"""
|
1956
|
+
|
1957
|
+
current_block, genesis_block = await asyncio.gather(
|
1958
|
+
subtensor.substrate.get_block_number(), subtensor.substrate.get_block_hash(0)
|
1959
|
+
)
|
1960
|
+
if (
|
1961
|
+
current_block - start_block > 300
|
1962
|
+
and genesis_block == Constants.genesis_block_hash_map["finney"]
|
1963
|
+
):
|
1964
|
+
console.print("Querying archive node for coldkey swap events...")
|
1965
|
+
await subtensor.substrate.close()
|
1966
|
+
subtensor = SubtensorInterface("archive")
|
1967
|
+
|
1968
|
+
block_hashes = await asyncio.gather(
|
1969
|
+
*[
|
1970
|
+
subtensor.substrate.get_block_hash(block_num)
|
1971
|
+
for block_num in range(start_block, end_block + 1)
|
1972
|
+
]
|
1973
|
+
)
|
1974
|
+
block_events = await asyncio.gather(
|
1975
|
+
*[
|
1976
|
+
subtensor.substrate.get_events(block_hash=block_hash)
|
1977
|
+
for block_hash in block_hashes
|
1978
|
+
]
|
1979
|
+
)
|
1980
|
+
|
1981
|
+
for block_num, events in zip(range(start_block, end_block + 1), block_events):
|
1982
|
+
for event in events:
|
1983
|
+
if (
|
1984
|
+
event.get("event", {}).get("module_id") == "SubtensorModule"
|
1985
|
+
and event.get("event", {}).get("event_id") == "ColdkeySwapScheduled"
|
1986
|
+
):
|
1987
|
+
attributes = event["event"].get("attributes", {})
|
1988
|
+
old_coldkey = decode_account_id(attributes["old_coldkey"][0])
|
1989
|
+
|
1990
|
+
if old_coldkey == wallet_ss58:
|
1991
|
+
return {
|
1992
|
+
"block_num": block_num,
|
1993
|
+
"dest_coldkey": decode_account_id(attributes["new_coldkey"][0]),
|
1994
|
+
"execution_block": attributes["execution_block"],
|
1995
|
+
}
|
1996
|
+
|
1997
|
+
return {}
|
1998
|
+
|
1999
|
+
|
2000
|
+
async def check_swap_status(
|
2001
|
+
subtensor: SubtensorInterface,
|
2002
|
+
origin_ss58: Optional[str] = None,
|
2003
|
+
expected_block_number: Optional[int] = None,
|
2004
|
+
) -> None:
|
2005
|
+
"""
|
2006
|
+
Check the status of a coldkey swap.
|
2007
|
+
|
2008
|
+
Args:
|
2009
|
+
subtensor: Connection to the network
|
2010
|
+
origin_ss58: The SS58 address of the original coldkey
|
2011
|
+
block_number: Optional block number where the swap was scheduled
|
2012
|
+
"""
|
2013
|
+
scheduled_swaps = await subtensor.get_scheduled_coldkey_swap()
|
2014
|
+
|
2015
|
+
if not origin_ss58:
|
2016
|
+
if not scheduled_swaps:
|
2017
|
+
console.print("[yellow]No pending coldkey swaps found.[/yellow]")
|
2018
|
+
return
|
2019
|
+
|
2020
|
+
table = Table(
|
2021
|
+
Column(
|
2022
|
+
"Original Coldkey",
|
2023
|
+
justify="Left",
|
2024
|
+
style=COLOR_PALETTE["GENERAL"]["SUBHEADING_MAIN"],
|
2025
|
+
no_wrap=True,
|
2026
|
+
),
|
2027
|
+
Column("Status", style="dark_sea_green3"),
|
2028
|
+
title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Pending Coldkey Swaps\n",
|
2029
|
+
show_header=True,
|
2030
|
+
show_edge=False,
|
2031
|
+
header_style="bold white",
|
2032
|
+
border_style="bright_black",
|
2033
|
+
style="bold",
|
2034
|
+
title_justify="center",
|
2035
|
+
show_lines=False,
|
2036
|
+
pad_edge=True,
|
2037
|
+
)
|
2038
|
+
|
2039
|
+
for coldkey in scheduled_swaps:
|
2040
|
+
table.add_row(coldkey, "Pending")
|
2041
|
+
|
2042
|
+
console.print(table)
|
2043
|
+
console.print(
|
2044
|
+
"\n[dim]Tip: Check specific swap details by providing the original coldkey SS58 address and the block number.[/dim]"
|
2045
|
+
)
|
2046
|
+
return
|
2047
|
+
|
2048
|
+
is_pending = origin_ss58 in scheduled_swaps
|
2049
|
+
|
2050
|
+
if not is_pending:
|
2051
|
+
console.print(
|
2052
|
+
f"[red]No pending swap found for coldkey:[/red] [{COLORS.G.CK}]{origin_ss58}[/{COLORS.G.CK}]"
|
2053
|
+
)
|
2054
|
+
return
|
2055
|
+
|
2056
|
+
console.print(
|
2057
|
+
f"[green]Found pending swap for coldkey:[/green] [{COLORS.G.CK}]{origin_ss58}[/{COLORS.G.CK}]"
|
2058
|
+
)
|
2059
|
+
|
2060
|
+
if expected_block_number is None:
|
2061
|
+
return
|
2062
|
+
|
2063
|
+
swap_info = await find_coldkey_swap_extrinsic(
|
2064
|
+
subtensor=subtensor,
|
2065
|
+
start_block=expected_block_number,
|
2066
|
+
end_block=expected_block_number,
|
2067
|
+
wallet_ss58=origin_ss58,
|
2068
|
+
)
|
2069
|
+
|
2070
|
+
if not swap_info:
|
2071
|
+
console.print(
|
2072
|
+
f"[yellow]Warning: Could not find swap extrinsic at block {expected_block_number}[/yellow]"
|
2073
|
+
)
|
2074
|
+
return
|
2075
|
+
|
2076
|
+
current_block = await subtensor.substrate.get_block_number()
|
2077
|
+
remaining_blocks = swap_info["execution_block"] - current_block
|
2078
|
+
|
2079
|
+
if remaining_blocks <= 0:
|
2080
|
+
console.print("[green]Swap period has completed![/green]")
|
2081
|
+
return
|
2082
|
+
|
2083
|
+
console.print(
|
2084
|
+
"\n[green]Coldkey swap details:[/green]"
|
2085
|
+
f"\nScheduled at block: {swap_info['block_num']}"
|
2086
|
+
f"\nOriginal address: [{COLORS.G.CK}]{origin_ss58}[/{COLORS.G.CK}]"
|
2087
|
+
f"\nDestination address: [{COLORS.G.CK}]{swap_info['dest_coldkey']}[/{COLORS.G.CK}]"
|
2088
|
+
f"\nCompletion block: {swap_info['execution_block']}"
|
2089
|
+
f"\nTime remaining: {blocks_to_duration(remaining_blocks)}"
|
2090
|
+
)
|