def select_stake(stakeholder: StakeHolder, emitter: StdoutEmitter, divisible: bool = False, staker_address: str = None ) -> Stake: """Interactively select a stake or abort if there are no eligible stakes.""" # Precondition: Active Stakes if staker_address: staker = stakeholder.get_staker(checksum_address=staker_address) stakes = staker.stakes else: stakes = stakeholder.all_stakes if not stakes: emitter.echo(NO_STAKES_FOUND, color='red') raise click.Abort # Precondition: Divisible Stakes stakes = stakeholder.sorted_stakes if divisible: emitter.echo(ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE, color='yellow') stakes = stakeholder.divisible_stakes if not stakes: emitter.echo(NO_DIVISIBLE_STAKES, color='red') raise click.Abort # Interactive Selection enumerated_stakes = dict(enumerate(stakes)) paint_stakes(stakeholder=stakeholder, emitter=emitter, staker_address=staker_address) choice = click.prompt(SELECT_STAKE, type=click.IntRange(min=0, max=len(enumerated_stakes)-1)) chosen_stake = enumerated_stakes[choice] return chosen_stake
def select_stake(staker: Staker, emitter: StdoutEmitter, stakes_status: Stake.Status = Stake.Status.EDITABLE, filter_function: Callable[[Stake], bool] = None) -> Stake: """Interactively select a stake or abort if there are no eligible stakes.""" if stakes_status.is_child(Stake.Status.DIVISIBLE): emitter.echo(ONLY_DISPLAYING_DIVISIBLE_STAKES_NOTE, color='yellow') # Filter stakes by status stakes = staker.sorted_stakes(parent_status=stakes_status, filter_function=filter_function) if not stakes: emitter.echo(NO_STAKES_FOUND, color='red') raise click.Abort # Interactive Selection paint_unlocked = stakes_status.is_child(Stake.Status.UNLOCKED) paint_stakes(staker=staker, emitter=emitter, stakes=stakes, paint_unlocked=paint_unlocked) indexed_stakes = {stake.index: stake for stake in stakes} indices = [str(index) for index in indexed_stakes.keys()] choice = click.prompt(SELECT_STAKE, type=click.Choice(indices)) chosen_stake = indexed_stakes[int(choice)] return chosen_stake
def prolong(general_config, transacting_staker_options, config_file, force, lock_periods, index): """Prolong an existing stake's duration.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character(emitter, config_file) action_period = STAKEHOLDER.staking_agent.get_current_period() blockchain = transacting_staker_options.get_blockchain() economics = STAKEHOLDER.economics # Handle account selection client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options.staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # Handle stake update and selection if index is not None: # 0 is valid. current_stake = STAKEHOLDER.stakes[index] else: current_stake = select_stake(staker=STAKEHOLDER, emitter=emitter) # # Prolong # # Interactive if not lock_periods: max_extension = MAX_UINT16 - current_stake.final_locked_period # +1 because current period excluded min_extension = economics.minimum_locked_periods - current_stake.periods_remaining + 1 if min_extension < 1: min_extension = 1 duration_extension_range = click.IntRange(min=min_extension, max=max_extension, clamp=False) lock_periods = click.prompt(PROMPT_PROLONG_VALUE.format(minimum=min_extension, maximum=max_extension), type=duration_extension_range) if not force: click.confirm(CONFIRM_PROLONG.format(lock_periods=lock_periods), abort=True) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(password=password) # Non-interactive: Consistency check to prevent the above agreement from going stale. last_second_current_period = STAKEHOLDER.staking_agent.get_current_period() if action_period != last_second_current_period: emitter.echo(PERIOD_ADVANCED_WARNING, color='red') raise click.Abort # Execute receipt = STAKEHOLDER.prolong_stake(stake=current_stake, additional_periods=lock_periods) # Report emitter.echo(SUCCESSFUL_STAKE_PROLONG, color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name) paint_stakes(emitter=emitter, staker=STAKEHOLDER)
def paintStakes(self): """ Display a list of all active stakes. """ if self.ursula.stakes: from nucypher.cli.painting.staking import paint_stakes paint_stakes(self.emitter, stakes=self.ursula.stakes) else: self.emitter.echo("No active stakes.")
def merge(general_config, transacting_staker_options, config_file, force, index_1, index_2): """Merge two stakes into one.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character(emitter, config_file) action_period = STAKEHOLDER.staking_agent.get_current_period() blockchain = transacting_staker_options.get_blockchain() client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options.staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # Handle stakes selection stake_1, stake_2 = None, None if index_1 is not None and index_2 is not None: stake_1 = STAKEHOLDER.stakes[index_1] stake_2 = STAKEHOLDER.stakes[index_2] elif index_1 is not None: # 0 is valid. stake_1 = STAKEHOLDER.stakes[index_1] elif index_2 is not None: stake_1 = STAKEHOLDER.stakes[index_2] if stake_1 is None: stake_1 = select_stake(staker=STAKEHOLDER, emitter=emitter) if stake_2 is None: emitter.echo(ONLY_DISPLAYING_MERGEABLE_STAKES_NOTE.format(final_period=stake_1.final_locked_period), color='yellow') stake_2 = select_stake(staker=STAKEHOLDER, emitter=emitter, filter_function=lambda s: s.index != stake_1.index and s.final_locked_period == stake_1.final_locked_period) if not force: click.confirm(CONFIRM_MERGE.format(stake_index_1=stake_1.index, stake_index_2=stake_2.index), abort=True) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(password=password) # Non-interactive: Consistency check to prevent the above agreement from going stale. last_second_current_period = STAKEHOLDER.staking_agent.get_current_period() if action_period != last_second_current_period: emitter.echo(PERIOD_ADVANCED_WARNING, color='red') raise click.Abort # Execute receipt = STAKEHOLDER.merge_stakes(stake_1=stake_1, stake_2=stake_2) # Report emitter.echo(SUCCESSFUL_STAKES_MERGE, color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name) paint_stakes(emitter=emitter, staker=STAKEHOLDER)
def stakers(general_config, registry_options, staking_address, substakes): """Show relevant information about stakers.""" if substakes and not staking_address: raise click.BadOptionUsage( option_name="--substakes", message= "--substakes is only valid when used with --staking-address.") emitter, registry, blockchain = registry_options.setup( general_config=general_config) staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry) stakers_list = [staking_address ] if staking_address else staking_agent.get_stakers() paint_stakers(emitter=emitter, stakers=stakers_list, registry=registry) if substakes: staker = Staker(registry=registry, domain=registry_options.network, checksum_address=staking_address) staker.stakes.refresh() paint_stakes(emitter=emitter, staker=staker, paint_unlocked=True)
def remove_inactive_substake(emitter, stakeholder: StakeHolder, action_period: int, stake: Stake, chain_name: str, force: bool) -> None: # Non-interactive: Consistency check to prevent the above agreement from going stale. last_second_current_period = stakeholder.staker.staking_agent.get_current_period( ) if action_period != last_second_current_period: emitter.echo(PERIOD_ADVANCED_WARNING, color='red') raise click.Abort if not force: click.confirm(CONFIRM_REMOVE_SUBSTAKE.format(stake_index=stake.index), abort=True) # Execute receipt = stakeholder.staker.remove_inactive_stake(stake=stake) # Report emitter.echo(SUCCESSFUL_STAKE_REMOVAL, color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=chain_name) paint_stakes(emitter=emitter, staker=stakeholder.staker)
def divide(general_config, transacting_staker_options, config_file, force, value, lock_periods, index): """Create a new stake from part of an existing one.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() economics = STAKEHOLDER.economics action_period = STAKEHOLDER.staking_agent.get_current_period() client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options. staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # Dynamic click types (Economics) min_locked = economics.minimum_allowed_locked stake_value_range = click.FloatRange( min=NU.from_nunits(min_locked).to_tokens(), clamp=False) if transacting_staker_options.staker_options.staking_address and index is not None: # 0 is valid. STAKEHOLDER.stakes = StakeList( registry=STAKEHOLDER.registry, checksum_address=transacting_staker_options.staker_options. staking_address) STAKEHOLDER.stakes.refresh() current_stake = STAKEHOLDER.stakes[index] else: current_stake = select_stake(stakeholder=STAKEHOLDER, emitter=emitter, divisible=True, staker_address=client_account) # # Stage Stake # # Value if not value: min_allowed_locked = NU.from_nunits( STAKEHOLDER.economics.minimum_allowed_locked) max_divide_value = max(min_allowed_locked, current_stake.value - min_allowed_locked) prompt = PROMPT_STAKE_DIVIDE_VALUE.format( minimm=min_allowed_locked, maximum=str(max_divide_value)) value = click.prompt(prompt, type=stake_value_range) value = NU(value, 'NU') # Duration if not lock_periods: max_extension = MAX_UINT16 - current_stake.final_locked_period divide_extension_range = click.IntRange(min=1, max=max_extension, clamp=False) extension = click.prompt(PROMPT_STAKE_EXTEND_VALUE, type=divide_extension_range) else: extension = lock_periods if not force: confirm_large_stake(lock_periods=extension, value=value) paint_staged_stake_division(emitter=emitter, stakeholder=STAKEHOLDER, original_stake=current_stake, target_value=value, extension=extension) click.confirm(CONFIRM_BROADCAST_STAKE_DIVIDE, abort=True) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) # Consistency check to prevent the above agreement from going stale. last_second_current_period = STAKEHOLDER.staking_agent.get_current_period() if action_period != last_second_current_period: emitter.echo(PERIOD_ADVANCED_WARNING, red='red') raise click.Abort # Execute STAKEHOLDER.assimilate(checksum_address=current_stake.staker_address, password=password) modified_stake, new_stake = STAKEHOLDER.divide_stake( stake_index=current_stake.index, target_value=value, additional_periods=extension) emitter.echo(SUCCESSFUL_STAKE_DIVIDE, color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=new_stake.receipt, chain_name=blockchain.client.chain_name) # Show the resulting stake list paint_stakes(emitter=emitter, stakeholder=STAKEHOLDER)
def list_stakes(general_config, staker_options, config_file, all): """List active stakes for current stakeholder.""" emitter = setup_emitter(general_config) STAKEHOLDER = staker_options.create_character(emitter, config_file) paint_stakes(emitter=emitter, stakeholder=STAKEHOLDER, paint_inactive=all)
def increase(general_config, transacting_staker_options, config_file, force, value, index, only_lock): """Increase an existing stake.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character(emitter, config_file) blockchain = transacting_staker_options.get_blockchain() client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options.staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # Handle stake update and selection if index is not None: # 0 is valid. current_stake = STAKEHOLDER.stakes[index] else: current_stake = select_stake(staker=STAKEHOLDER, emitter=emitter) # # Stage Stake # if not value: if not only_lock: only_lock = not click.prompt(PROMPT_DEPOSIT_OR_LOCK, type=click.BOOL, default=True) token_balance = STAKEHOLDER.token_balance if not only_lock else STAKEHOLDER.calculate_staking_reward() locked_tokens = STAKEHOLDER.locked_tokens(periods=1).to_nunits() upper_limit = min(token_balance, NU.from_nunits(STAKEHOLDER.economics.maximum_allowed_locked - locked_tokens)) if token_balance == 0: emitter.echo(INSUFFICIENT_BALANCE_TO_INCREASE, color='red') raise click.Abort if upper_limit == 0: emitter.echo(MAXIMUM_STAKE_REACHED, color='red') raise click.Abort stake_value_range = click.FloatRange(min=0, max=upper_limit.to_tokens(), clamp=False) value = click.prompt(PROMPT_STAKE_INCREASE_VALUE.format(upper_limit=upper_limit), type=stake_value_range) value = NU.from_tokens(value) # # Review and Publish # if not force: lock_periods = current_stake.periods_remaining - 1 current_period = STAKEHOLDER.staking_agent.get_current_period() unlock_period = current_stake.final_locked_period + 1 confirm_large_stake(value=value, lock_periods=lock_periods) paint_staged_stake(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, stake_value=value, lock_periods=lock_periods, start_period=current_period + 1, unlock_period=unlock_period) click.confirm(CONFIRM_INCREASING_STAKE.format(stake_index=current_stake.index, value=value), abort=True) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(password=password) # Execute receipt = STAKEHOLDER.increase_stake(stake=current_stake, amount=value, only_lock=only_lock) # Report emitter.echo(SUCCESSFUL_STAKE_INCREASE, color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name) paint_stakes(emitter=emitter, staker=STAKEHOLDER)