communex.cli.key
1import json 2import re 3from enum import Enum 4from typing import Any, Optional, cast 5 6import typer 7from substrateinterface import Keypair # type: ignore 8from typer import Context 9 10from communex._common import BalanceUnit, format_balance 11from communex.cli._common import (get_universal_password, make_custom_context, 12 print_table_from_plain_dict, 13 print_table_standardize) 14from communex.compat.key import (classic_key_path, classic_store_key, 15 local_key_addresses, try_classic_load_key, 16 try_load_key) 17from communex.key import check_ss58_address, generate_keypair, is_ss58_address 18from communex.misc import (local_keys_allbalance, local_keys_to_freebalance, 19 local_keys_to_stakedbalance) 20 21key_app = typer.Typer(no_args_is_help=True) 22 23 24class SortBalance(str, Enum): 25 all = "all" 26 free = "free" 27 staked = "staked" 28 29 30@key_app.command() 31def create( 32 ctx: Context, name: str, 33 password: str = typer.Option(None) 34): 35 """ 36 Generates a new key and stores it on a disk with the given name. 37 """ 38 context = make_custom_context(ctx) 39 40 keypair = generate_keypair() 41 address = keypair.ss58_address 42 43 context.info(f"Generated key with public address '{address}'.") 44 45 classic_store_key(keypair, name, password) 46 47 context.info(f"Key successfully stored with name '{name}'.") 48 49 50@key_app.command() 51def regen( 52 ctx: Context, name: str, key_input: str, password: Optional[str] = None): 53 """ 54 Stores the given key on a disk. Works with private key or mnemonic. 55 """ 56 # TODO: secret input from env var and stdin 57 context = make_custom_context(ctx) 58 # Determine the input type based on the presence of spaces. 59 if re.search(r'\s', key_input): 60 # If mnemonic (contains spaces between words). 61 keypair = Keypair.create_from_mnemonic(key_input) 62 key_type = "mnemonic" 63 else: 64 # If private key (assumes no spaces). 65 keypair = Keypair.create_from_private_key(key_input, ss58_format=42) 66 key_type = "private key" 67 # Substrate does not return these. 68 keypair.mnemonic = "" # type: ignore 69 keypair.seed_hex = "" 70 71 address = keypair.ss58_address 72 context.info(f"Loaded {key_type} with public address `{address}`.") 73 74 classic_store_key(keypair, name, password) 75 76 context.info(f"Key stored with name `{name}` successfully.") 77 78 79@key_app.command() 80def show( 81 ctx: Context, key: str, show_private: bool = False, 82 password: Optional[str] = None 83): 84 """ 85 Show information about a key. 86 """ 87 context = make_custom_context(ctx) 88 89 path = classic_key_path(key) 90 key_dict_json = try_load_key(path, context, password=password) 91 key_dict = json.loads(key_dict_json) 92 93 if show_private is not True: 94 key_dict["private_key"] = "[SENSITIVE-MODE]" 95 key_dict["seed_hex"] = "[SENSITIVE-MODE]" 96 key_dict["mnemonic"] = "[SENSITIVE-MODE]" 97 98 print_table_from_plain_dict(key_dict, ["Key", "Value"], context.console) 99 100 101@key_app.command() 102def balances( 103 ctx: Context, 104 unit: BalanceUnit = BalanceUnit.joule, 105 sort_balance: SortBalance = SortBalance.all, 106 use_universal_password: bool = typer.Option( 107 False, help=""" 108 If you want to use a password to decrypt all keys. 109 This will only work if all encrypted keys uses the same password. 110 If this is not the case, leave it blank and you will be prompted to give 111 every password. 112 """ 113 ) 114 115): 116 """ 117 Gets balances of all keys. 118 """ 119 context = make_custom_context(ctx) 120 client = context.com_client() 121 if use_universal_password: 122 universal_password = get_universal_password(context) 123 else: 124 universal_password = None 125 126 local_keys = local_key_addresses(context, universal_password=universal_password) 127 with context.console.status("Getting balances of all keys, this might take a while..."): 128 key2freebalance, key2stake = local_keys_allbalance(client, local_keys) 129 key_to_freebalance = {k: format_balance( 130 v, unit) for k, v in key2freebalance.items()} 131 key_to_stake = {k: format_balance(v, unit) for k, v in key2stake.items()} 132 133 key2balance = {k: v + key2stake[k] for k, v in key2freebalance.items()} 134 key_to_balance = {k: format_balance(v, unit) 135 for k, v in key2balance.items()} 136 137 if sort_balance == SortBalance.all: 138 sorted_bal = {k: v for k, v in sorted( 139 key2balance.items(), key=lambda item: item[1], reverse=True)} 140 elif sort_balance == SortBalance.free: 141 sorted_bal = {k: v for k, v in sorted( 142 key2freebalance.items(), key=lambda item: item[1], reverse=True)} 143 elif sort_balance == SortBalance.staked: 144 sorted_bal = {k: v for k, v in sorted( 145 key2stake.items(), key=lambda item: item[1], reverse=True)} 146 else: 147 raise ValueError("Invalid sort balance option") 148 149 stake: list[str] = [] 150 all_balance: list[str] = [] 151 free: list[str] = [] 152 keys: list[str] = [] 153 154 for key, _ in sorted_bal.items(): 155 keys.append(key) 156 free.append(key_to_freebalance[key]) 157 stake.append(key_to_stake[key]) 158 all_balance.append(key_to_balance[key]) 159 160 pretty_dict = { 161 "key": keys, 162 "free": free, 163 "staked": stake, 164 "all": all_balance, 165 } 166 167 general_dict: dict[str, list[Any]] = cast( 168 dict[str, list[Any]], pretty_dict) 169 print_table_standardize(general_dict, context.console) 170 171 172@key_app.command(name='list') 173def inventory( 174 ctx: Context, 175 use_universal_password: bool = typer.Option( 176 False, help=""" 177 Password to decrypt all keys. 178 This will only work if all encrypted keys uses the same password. 179 If this is not the case, leave it blank and you will be prompted to give 180 every password. 181 """ 182 ) 183): 184 """ 185 Lists all keys stored on disk. 186 """ 187 context = make_custom_context(ctx) 188 if use_universal_password: 189 universal_password = get_universal_password(context) 190 else: 191 universal_password = None 192 key_to_address = local_key_addresses(context, universal_password) 193 general_key_to_address: dict[str, str] = cast( 194 dict[str, str], key_to_address) 195 print_table_from_plain_dict(general_key_to_address, [ 196 "Key", "Address"], context.console) 197 198 199@key_app.command() 200def stakefrom( 201 ctx: Context, key: str, 202 unit: BalanceUnit = BalanceUnit.joule, 203 password: Optional[str] = None, 204): 205 """ 206 Gets what keys is key staked from. 207 """ 208 context = make_custom_context(ctx) 209 client = context.com_client() 210 211 if is_ss58_address(key): 212 key_address = key 213 else: 214 keypair = try_classic_load_key(key, context, password) 215 key_address = keypair.ss58_address 216 key_address = check_ss58_address(key_address) 217 with context.progress_status(f"Getting stake-from map for {key_address}..."): 218 result = client.get_stakefrom(key=key_address) 219 220 result = {k: format_balance(v, unit) for k, v in result.items()} 221 222 print_table_from_plain_dict(result, ["Key", "Stake"], context.console) 223 224 225@key_app.command() 226def staketo( 227 ctx: Context, key: str, 228 unit: BalanceUnit = BalanceUnit.joule, 229 password: Optional[str] = None, 230): 231 """ 232 Gets stake to a key. 233 """ 234 context = make_custom_context(ctx) 235 client = context.com_client() 236 237 if is_ss58_address(key): 238 key_address = key 239 else: 240 keypair = try_classic_load_key(key, context, password) 241 key_address = keypair.ss58_address 242 key_address = check_ss58_address(key_address) 243 244 with context.progress_status(f"Getting stake-to of {key_address}..."): 245 result = client.get_staketo(key=key_address) 246 247 result = {k: format_balance(v, unit) for k, v in result.items()} 248 249 print_table_from_plain_dict(result, ["Key", "Stake"], context.console) 250 251 252@key_app.command() 253def total_free_balance( 254 ctx: Context, 255 unit: BalanceUnit = BalanceUnit.joule, 256 use_universal_password: Optional[str] = typer.Option( 257 False, help=""" 258 Password to decrypt all keys. 259 This will only work if all encrypted keys uses the same password. 260 If this is not the case, leave it blank and you will be prompted to give 261 every password. 262 """ 263 ) 264): 265 """ 266 Returns total balance of all keys on a disk 267 """ 268 context = make_custom_context(ctx) 269 client = context.com_client() 270 271 if use_universal_password: 272 universal_password = get_universal_password(context) 273 else: 274 universal_password = None 275 local_keys = local_key_addresses(context, universal_password) 276 with context.progress_status("Getting total free balance of all keys..."): 277 key2balance: dict[str, int] = local_keys_to_freebalance(client, local_keys) 278 279 balance_sum = sum(key2balance.values()) 280 281 context.output(format_balance(balance_sum, unit=unit)) 282 283 284@key_app.command() 285def total_staked_balance( 286 ctx: Context, unit: BalanceUnit = BalanceUnit.joule, 287 use_universal_password: bool = typer.Option( 288 False, help=""" 289 Password to decrypt all keys. 290 This will only work if all encrypted keys uses the same password. 291 If this is not the case, leave it blank and you will be prompted to give 292 every password. 293 """ 294 ) 295): 296 """ 297 Returns total stake of all keys on a disk 298 """ 299 context = make_custom_context(ctx) 300 client = context.com_client() 301 302 if use_universal_password: 303 universal_password = get_universal_password(context) 304 else: 305 universal_password = None 306 local_keys = local_key_addresses(context, universal_password) 307 with context.progress_status("Getting total staked balance of all keys..."): 308 key2stake: dict[str, int] = local_keys_to_stakedbalance( 309 client, local_keys, 310 ) 311 312 stake_sum = sum(key2stake.values()) 313 314 context.output(format_balance(stake_sum, unit=unit)) 315 316 317@key_app.command() 318def total_balance( 319 ctx: Context, unit: BalanceUnit = BalanceUnit.joule, 320 use_universal_password: bool = typer.Option( 321 False, help=""" 322 Password to decrypt all keys. 323 This will only work if all encrypted keys uses the same password. 324 If this is not the case, leave it blank and you will be prompted to give 325 every password. 326 """ 327 ) 328): 329 """ 330 Returns total tokens of all keys on a disk 331 """ 332 context = make_custom_context(ctx) 333 client = context.com_client() 334 335 if use_universal_password: 336 universal_password = get_universal_password(context) 337 else: 338 universal_password = None 339 local_keys = local_key_addresses(context, universal_password) 340 with context.progress_status("Getting total tokens of all keys..."): 341 key2balance, key2stake = local_keys_allbalance( 342 client, local_keys 343 ) 344 key2tokens = {k: v + key2stake[k] for k, v in key2balance.items()} 345 tokens_sum = sum(key2tokens.values()) 346 347 context.output(format_balance(tokens_sum, unit=unit)) 348 349 350@key_app.command() 351def power_delegation( 352 ctx: Context, 353 key: Optional[str] = None, 354 enable: bool = typer.Option(True, "--disable"), 355 use_universal_password: bool = typer.Option(False) 356): 357 """ 358 Gets power delegation of a key. 359 """ 360 context = make_custom_context(ctx) 361 client = context.com_client() 362 if key is None: 363 action = "enable" if enable else "disable" 364 confirm_message = ( 365 f"Key was not set, this will {action} vote power delegation for all" 366 " keys on disk. Do you want to proceed?" 367 ) 368 if not typer.confirm(confirm_message): 369 context.info("Aborted.") 370 exit(0) 371 372 if use_universal_password: 373 universal_password = get_universal_password(context) 374 else: 375 universal_password = None 376 local_keys = local_key_addresses(context, universal_password) 377 else: 378 local_keys = {key: None} 379 for key_name in local_keys.keys(): 380 keypair = try_classic_load_key(key_name, context) 381 if enable is True: 382 context.info(f"Enabling vote power delegation on key {key_name} ...") 383 client.enable_vote_power_delegation(keypair) 384 else: 385 context.info(f"Disabling vote power delegation on key {key_name} ...") 386 client.disable_vote_power_delegation(keypair)
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
31@key_app.command() 32def create( 33 ctx: Context, name: str, 34 password: str = typer.Option(None) 35): 36 """ 37 Generates a new key and stores it on a disk with the given name. 38 """ 39 context = make_custom_context(ctx) 40 41 keypair = generate_keypair() 42 address = keypair.ss58_address 43 44 context.info(f"Generated key with public address '{address}'.") 45 46 classic_store_key(keypair, name, password) 47 48 context.info(f"Key successfully stored with name '{name}'.")
Generates a new key and stores it on a disk with the given name.
51@key_app.command() 52def regen( 53 ctx: Context, name: str, key_input: str, password: Optional[str] = None): 54 """ 55 Stores the given key on a disk. Works with private key or mnemonic. 56 """ 57 # TODO: secret input from env var and stdin 58 context = make_custom_context(ctx) 59 # Determine the input type based on the presence of spaces. 60 if re.search(r'\s', key_input): 61 # If mnemonic (contains spaces between words). 62 keypair = Keypair.create_from_mnemonic(key_input) 63 key_type = "mnemonic" 64 else: 65 # If private key (assumes no spaces). 66 keypair = Keypair.create_from_private_key(key_input, ss58_format=42) 67 key_type = "private key" 68 # Substrate does not return these. 69 keypair.mnemonic = "" # type: ignore 70 keypair.seed_hex = "" 71 72 address = keypair.ss58_address 73 context.info(f"Loaded {key_type} with public address `{address}`.") 74 75 classic_store_key(keypair, name, password) 76 77 context.info(f"Key stored with name `{name}` successfully.")
Stores the given key on a disk. Works with private key or mnemonic.
80@key_app.command() 81def show( 82 ctx: Context, key: str, show_private: bool = False, 83 password: Optional[str] = None 84): 85 """ 86 Show information about a key. 87 """ 88 context = make_custom_context(ctx) 89 90 path = classic_key_path(key) 91 key_dict_json = try_load_key(path, context, password=password) 92 key_dict = json.loads(key_dict_json) 93 94 if show_private is not True: 95 key_dict["private_key"] = "[SENSITIVE-MODE]" 96 key_dict["seed_hex"] = "[SENSITIVE-MODE]" 97 key_dict["mnemonic"] = "[SENSITIVE-MODE]" 98 99 print_table_from_plain_dict(key_dict, ["Key", "Value"], context.console)
Show information about a key.
102@key_app.command() 103def balances( 104 ctx: Context, 105 unit: BalanceUnit = BalanceUnit.joule, 106 sort_balance: SortBalance = SortBalance.all, 107 use_universal_password: bool = typer.Option( 108 False, help=""" 109 If you want to use a password to decrypt all keys. 110 This will only work if all encrypted keys uses the same password. 111 If this is not the case, leave it blank and you will be prompted to give 112 every password. 113 """ 114 ) 115 116): 117 """ 118 Gets balances of all keys. 119 """ 120 context = make_custom_context(ctx) 121 client = context.com_client() 122 if use_universal_password: 123 universal_password = get_universal_password(context) 124 else: 125 universal_password = None 126 127 local_keys = local_key_addresses(context, universal_password=universal_password) 128 with context.console.status("Getting balances of all keys, this might take a while..."): 129 key2freebalance, key2stake = local_keys_allbalance(client, local_keys) 130 key_to_freebalance = {k: format_balance( 131 v, unit) for k, v in key2freebalance.items()} 132 key_to_stake = {k: format_balance(v, unit) for k, v in key2stake.items()} 133 134 key2balance = {k: v + key2stake[k] for k, v in key2freebalance.items()} 135 key_to_balance = {k: format_balance(v, unit) 136 for k, v in key2balance.items()} 137 138 if sort_balance == SortBalance.all: 139 sorted_bal = {k: v for k, v in sorted( 140 key2balance.items(), key=lambda item: item[1], reverse=True)} 141 elif sort_balance == SortBalance.free: 142 sorted_bal = {k: v for k, v in sorted( 143 key2freebalance.items(), key=lambda item: item[1], reverse=True)} 144 elif sort_balance == SortBalance.staked: 145 sorted_bal = {k: v for k, v in sorted( 146 key2stake.items(), key=lambda item: item[1], reverse=True)} 147 else: 148 raise ValueError("Invalid sort balance option") 149 150 stake: list[str] = [] 151 all_balance: list[str] = [] 152 free: list[str] = [] 153 keys: list[str] = [] 154 155 for key, _ in sorted_bal.items(): 156 keys.append(key) 157 free.append(key_to_freebalance[key]) 158 stake.append(key_to_stake[key]) 159 all_balance.append(key_to_balance[key]) 160 161 pretty_dict = { 162 "key": keys, 163 "free": free, 164 "staked": stake, 165 "all": all_balance, 166 } 167 168 general_dict: dict[str, list[Any]] = cast( 169 dict[str, list[Any]], pretty_dict) 170 print_table_standardize(general_dict, context.console)
Gets balances of all keys.
173@key_app.command(name='list') 174def inventory( 175 ctx: Context, 176 use_universal_password: bool = typer.Option( 177 False, help=""" 178 Password to decrypt all keys. 179 This will only work if all encrypted keys uses the same password. 180 If this is not the case, leave it blank and you will be prompted to give 181 every password. 182 """ 183 ) 184): 185 """ 186 Lists all keys stored on disk. 187 """ 188 context = make_custom_context(ctx) 189 if use_universal_password: 190 universal_password = get_universal_password(context) 191 else: 192 universal_password = None 193 key_to_address = local_key_addresses(context, universal_password) 194 general_key_to_address: dict[str, str] = cast( 195 dict[str, str], key_to_address) 196 print_table_from_plain_dict(general_key_to_address, [ 197 "Key", "Address"], context.console)
Lists all keys stored on disk.
200@key_app.command() 201def stakefrom( 202 ctx: Context, key: str, 203 unit: BalanceUnit = BalanceUnit.joule, 204 password: Optional[str] = None, 205): 206 """ 207 Gets what keys is key staked from. 208 """ 209 context = make_custom_context(ctx) 210 client = context.com_client() 211 212 if is_ss58_address(key): 213 key_address = key 214 else: 215 keypair = try_classic_load_key(key, context, password) 216 key_address = keypair.ss58_address 217 key_address = check_ss58_address(key_address) 218 with context.progress_status(f"Getting stake-from map for {key_address}..."): 219 result = client.get_stakefrom(key=key_address) 220 221 result = {k: format_balance(v, unit) for k, v in result.items()} 222 223 print_table_from_plain_dict(result, ["Key", "Stake"], context.console)
Gets what keys is key staked from.
226@key_app.command() 227def staketo( 228 ctx: Context, key: str, 229 unit: BalanceUnit = BalanceUnit.joule, 230 password: Optional[str] = None, 231): 232 """ 233 Gets stake to a key. 234 """ 235 context = make_custom_context(ctx) 236 client = context.com_client() 237 238 if is_ss58_address(key): 239 key_address = key 240 else: 241 keypair = try_classic_load_key(key, context, password) 242 key_address = keypair.ss58_address 243 key_address = check_ss58_address(key_address) 244 245 with context.progress_status(f"Getting stake-to of {key_address}..."): 246 result = client.get_staketo(key=key_address) 247 248 result = {k: format_balance(v, unit) for k, v in result.items()} 249 250 print_table_from_plain_dict(result, ["Key", "Stake"], context.console)
Gets stake to a key.
253@key_app.command() 254def total_free_balance( 255 ctx: Context, 256 unit: BalanceUnit = BalanceUnit.joule, 257 use_universal_password: Optional[str] = typer.Option( 258 False, help=""" 259 Password to decrypt all keys. 260 This will only work if all encrypted keys uses the same password. 261 If this is not the case, leave it blank and you will be prompted to give 262 every password. 263 """ 264 ) 265): 266 """ 267 Returns total balance of all keys on a disk 268 """ 269 context = make_custom_context(ctx) 270 client = context.com_client() 271 272 if use_universal_password: 273 universal_password = get_universal_password(context) 274 else: 275 universal_password = None 276 local_keys = local_key_addresses(context, universal_password) 277 with context.progress_status("Getting total free balance of all keys..."): 278 key2balance: dict[str, int] = local_keys_to_freebalance(client, local_keys) 279 280 balance_sum = sum(key2balance.values()) 281 282 context.output(format_balance(balance_sum, unit=unit))
Returns total balance of all keys on a disk
285@key_app.command() 286def total_staked_balance( 287 ctx: Context, unit: BalanceUnit = BalanceUnit.joule, 288 use_universal_password: bool = typer.Option( 289 False, help=""" 290 Password to decrypt all keys. 291 This will only work if all encrypted keys uses the same password. 292 If this is not the case, leave it blank and you will be prompted to give 293 every password. 294 """ 295 ) 296): 297 """ 298 Returns total stake of all keys on a disk 299 """ 300 context = make_custom_context(ctx) 301 client = context.com_client() 302 303 if use_universal_password: 304 universal_password = get_universal_password(context) 305 else: 306 universal_password = None 307 local_keys = local_key_addresses(context, universal_password) 308 with context.progress_status("Getting total staked balance of all keys..."): 309 key2stake: dict[str, int] = local_keys_to_stakedbalance( 310 client, local_keys, 311 ) 312 313 stake_sum = sum(key2stake.values()) 314 315 context.output(format_balance(stake_sum, unit=unit))
Returns total stake of all keys on a disk
318@key_app.command() 319def total_balance( 320 ctx: Context, unit: BalanceUnit = BalanceUnit.joule, 321 use_universal_password: bool = typer.Option( 322 False, help=""" 323 Password to decrypt all keys. 324 This will only work if all encrypted keys uses the same password. 325 If this is not the case, leave it blank and you will be prompted to give 326 every password. 327 """ 328 ) 329): 330 """ 331 Returns total tokens of all keys on a disk 332 """ 333 context = make_custom_context(ctx) 334 client = context.com_client() 335 336 if use_universal_password: 337 universal_password = get_universal_password(context) 338 else: 339 universal_password = None 340 local_keys = local_key_addresses(context, universal_password) 341 with context.progress_status("Getting total tokens of all keys..."): 342 key2balance, key2stake = local_keys_allbalance( 343 client, local_keys 344 ) 345 key2tokens = {k: v + key2stake[k] for k, v in key2balance.items()} 346 tokens_sum = sum(key2tokens.values()) 347 348 context.output(format_balance(tokens_sum, unit=unit))
Returns total tokens of all keys on a disk
351@key_app.command() 352def power_delegation( 353 ctx: Context, 354 key: Optional[str] = None, 355 enable: bool = typer.Option(True, "--disable"), 356 use_universal_password: bool = typer.Option(False) 357): 358 """ 359 Gets power delegation of a key. 360 """ 361 context = make_custom_context(ctx) 362 client = context.com_client() 363 if key is None: 364 action = "enable" if enable else "disable" 365 confirm_message = ( 366 f"Key was not set, this will {action} vote power delegation for all" 367 " keys on disk. Do you want to proceed?" 368 ) 369 if not typer.confirm(confirm_message): 370 context.info("Aborted.") 371 exit(0) 372 373 if use_universal_password: 374 universal_password = get_universal_password(context) 375 else: 376 universal_password = None 377 local_keys = local_key_addresses(context, universal_password) 378 else: 379 local_keys = {key: None} 380 for key_name in local_keys.keys(): 381 keypair = try_classic_load_key(key_name, context) 382 if enable is True: 383 context.info(f"Enabling vote power delegation on key {key_name} ...") 384 client.enable_vote_power_delegation(keypair) 385 else: 386 context.info(f"Disabling vote power delegation on key {key_name} ...") 387 client.disable_vote_power_delegation(keypair)
Gets power delegation of a key.