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