Esempio n. 1
0
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 = handle_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 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)

    #
    # 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(f"Enter number of periods to extend ({min_extension}-{max_extension})",
                                    type=duration_extension_range)
    if not force:
        click.confirm(f"Publish stake extension of {lock_periods} period(s) to the blockchain?", abort=True)
    password = transacting_staker_options.get_password(blockchain, client_account)

    # 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("Current period advanced before transaction was broadcasted. Please try again.", red='red')
        raise click.Abort

    # Authenticate and Execute
    STAKEHOLDER.assimilate(checksum_address=current_stake.staker_address, password=password)
    receipt = STAKEHOLDER.prolong_stake(stake_index=current_stake.index, additional_periods=lock_periods)

    # Report
    emitter.echo('Successfully Prolonged Stake', color='green', verbosity=1)
    paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name)
    painting.paint_stakes(emitter=emitter, stakeholder=STAKEHOLDER)
    return  # Exit
Esempio n. 2
0
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 = handle_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)
        value = click.prompt(
            f"Enter target value ({min_allowed_locked} - {str(max_divide_value)})",
            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(f"Enter number of periods to extend",
                                 type=divide_extension_range)
    else:
        extension = lock_periods

    if not force:
        issue_stake_suggestions(lock_periods=extension, value=value)
        painting.paint_staged_stake_division(emitter=emitter,
                                             stakeholder=STAKEHOLDER,
                                             original_stake=current_stake,
                                             target_value=value,
                                             extension=extension)
        click.confirm("Publish stake division to the blockchain?", 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(
            "Current period advanced before stake division was broadcasted. Please try again.",
            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('Successfully divided stake', color='green', verbosity=1)
    paint_receipt_summary(emitter=emitter,
                          receipt=new_stake.receipt,
                          chain_name=blockchain.client.chain_name)

    # Show the resulting stake list
    painting.paint_stakes(emitter=emitter, stakeholder=STAKEHOLDER)
Esempio n. 3
0
def stake(click_config,
          action,

          config_root,
          config_file,

          # Mode
          force,
          offline,
          hw_wallet,

          # Blockchain
          poa,
          registry_filepath,
          provider_uri,
          sync,

          # Stake
          staking_address,
          worker_address,
          withdraw_address,
          value,
          lock_periods,
          index,
          policy_reward,
          staking_reward,
          enable,
          lock_until,

          ) -> None:
    """
    Manage stakes and other staker-related operations.

    \b
    Actions
    -------------------------------------------------
    init-stakeholder  Create a new stakeholder configuration
    list              List active stakes for current stakeholder
    accounts          Show ETH and NU balances for stakeholder's accounts
    sync              Synchronize stake data with on-chain information
    set-worker        Bond a worker to a staker
    detach-worker     Detach worker currently bonded to a staker
    init              Create a new stake
    restake           Manage re-staking with --enable or --disable
    divide            Create a new stake from part of an existing one
    collect-reward    Withdraw staking reward

    """

    # Banner
    emitter = click_config.emitter
    emitter.clear()
    emitter.banner(StakeHolder.banner)

    if action == 'init-stakeholder':

        if not provider_uri:
            raise click.BadOptionUsage(option_name='--provider',
                                       message="--provider is required to create a new stakeholder.")

        new_stakeholder = StakeHolderConfiguration.generate(config_root=config_root,
                                                            provider_uri=provider_uri,
                                                            poa=poa,
                                                            sync=False,
                                                            registry_filepath=registry_filepath)

        filepath = new_stakeholder.to_configuration_file(override=force)
        emitter.echo(f"Wrote new stakeholder configuration to {filepath}", color='green')
        return  # Exit

    try:
        stakeholder_config = StakeHolderConfiguration.from_configuration_file(filepath=config_file,
                                                                              provider_uri=provider_uri,
                                                                              poa=poa,
                                                                              sync=False,
                                                                              registry_filepath=registry_filepath)
    except FileNotFoundError:
        return actions.handle_missing_configuration_file(character_config_class=StakeHolderConfiguration,
                                                         config_file=config_file)

    #
    # Make Stakeholder
    #

    STAKEHOLDER = stakeholder_config.produce(initial_address=staking_address)
    blockchain = BlockchainInterfaceFactory.get_interface(provider_uri=provider_uri)  # Eager connection
    economics = STAKEHOLDER.economics

    # 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)
    stake_duration_range = click.IntRange(min=economics.minimum_locked_periods, clamp=False)
    stake_extension_range = click.IntRange(min=1, max=economics.maximum_allowed_locked, clamp=False)

    #
    # Eager Actions
    #

    if action == 'list':
        stakes = STAKEHOLDER.all_stakes
        if not stakes:
            emitter.echo(f"There are no active stakes")
        else:
            painting.paint_stakes(emitter=emitter, stakes=stakes)
        return  # Exit

    elif action == 'accounts':
        for address, balances in STAKEHOLDER.wallet.balances.items():
            emitter.echo(f"{address} | {Web3.fromWei(balances['ETH'], 'ether')} ETH | {NU.from_nunits(balances['NU'])}")
        return  # Exit

    elif action == 'set-worker':

        if not staking_address:
            staking_address = select_stake(stakeholder=STAKEHOLDER, emitter=emitter).staker_address

        if not worker_address:
            worker_address = click.prompt("Enter worker address", type=EIP55_CHECKSUM_ADDRESS)

        # TODO: Check preconditions (e.g., minWorkerPeriods, already in use, etc)

        password = None
        if not hw_wallet and not blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)

        STAKEHOLDER.assimilate(checksum_address=staking_address, password=password)
        receipt = STAKEHOLDER.set_worker(worker_address=worker_address)

        # TODO: Double-check dates
        current_period = STAKEHOLDER.staking_agent.get_current_period()
        bonded_date = datetime_at_period(period=current_period, seconds_per_period=economics.seconds_per_period)
        min_worker_periods = STAKEHOLDER.staking_agent.staking_parameters()[7]
        release_period = current_period + min_worker_periods
        release_date = datetime_at_period(period=release_period, seconds_per_period=economics.seconds_per_period)

        emitter.echo(f"\nWorker {worker_address} successfully bonded to staker {staking_address}", color='green')
        paint_receipt_summary(emitter=emitter,
                              receipt=receipt,
                              chain_name=blockchain.client.chain_name,
                              transaction_type='set_worker')
        emitter.echo(f"Bonded at period #{current_period} ({bonded_date})", color='green')
        emitter.echo(f"This worker can be replaced or detached after period "
                     f"#{release_period} ({release_date})", color='green')
        return  # Exit

    elif action == 'detach-worker':

        if not staking_address:
            staking_address = select_stake(stakeholder=STAKEHOLDER, emitter=emitter).staker_address

        if worker_address:
            raise click.BadOptionUsage(message="detach-worker cannot be used together with --worker-address",
                                       option_name='--worker-address')

        # TODO: Check preconditions (e.g., minWorkerPeriods)

        worker_address = STAKEHOLDER.staking_agent.get_worker_from_staker(staking_address)

        password = None
        if not hw_wallet and not blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)

        # TODO: Create Stakeholder.detach_worker() and use it here
        STAKEHOLDER.assimilate(checksum_address=staking_address, password=password)
        receipt = STAKEHOLDER.set_worker(worker_address=BlockchainInterface.NULL_ADDRESS)

        # TODO: Double-check dates
        current_period = STAKEHOLDER.staking_agent.get_current_period()
        bonded_date = datetime_at_period(period=current_period, seconds_per_period=economics.seconds_per_period)

        emitter.echo(f"Successfully detached worker {worker_address} from staker {staking_address}", color='green')
        paint_receipt_summary(emitter=emitter,
                              receipt=receipt,
                              chain_name=blockchain.client.chain_name,
                              transaction_type='detach_worker')
        emitter.echo(f"Detached at period #{current_period} ({bonded_date})", color='green')
        return  # Exit

    elif action == 'create':
        """Initialize a new stake"""

        #
        # Get Staking Account
        #

        password = None
        if not staking_address:
            staking_address = select_client_account(prompt="Select staking account",
                                                    emitter=emitter,
                                                    provider_uri=STAKEHOLDER.wallet.blockchain.provider_uri)

        if not hw_wallet and not blockchain.client.is_local:
            password = click.prompt(f"Enter password to unlock {staking_address}",
                                    hide_input=True,
                                    confirmation_prompt=False)
        #
        # Stage Stake
        #

        if not value:
            value = click.prompt(f"Enter stake value in NU",
                                 type=stake_value_range,
                                 default=NU.from_nunits(min_locked).to_tokens())
        value = NU.from_tokens(value)

        if not lock_periods:
            prompt = f"Enter stake duration ({STAKEHOLDER.economics.minimum_locked_periods} periods minimum)"
            lock_periods = click.prompt(prompt, type=stake_duration_range)

        start_period = STAKEHOLDER.staking_agent.get_current_period()
        end_period = start_period + lock_periods

        #
        # Review
        #

        if not force:
            painting.paint_staged_stake(emitter=emitter,
                                        stakeholder=STAKEHOLDER,
                                        staking_address=staking_address,
                                        stake_value=value,
                                        lock_periods=lock_periods,
                                        start_period=start_period,
                                        end_period=end_period)

            confirm_staged_stake(staker_address=staking_address, value=value, lock_periods=lock_periods)

        # Last chance to bail
        click.confirm("Publish staged stake to the blockchain?", abort=True)

        # Execute
        STAKEHOLDER.assimilate(checksum_address=staking_address, password=password)
        new_stake = STAKEHOLDER.initialize_stake(amount=value, lock_periods=lock_periods)

        painting.paint_staking_confirmation(emitter=emitter,
                                            ursula=STAKEHOLDER,
                                            transactions=new_stake.transactions)
        return  # Exit

    elif action == "restake":

        # Authenticate
        if not staking_address:
            staking_address = select_stake(stakeholder=STAKEHOLDER, emitter=emitter).staker_address
        password = None
        if not hw_wallet and not blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)
        STAKEHOLDER.assimilate(checksum_address=staking_address, password=password)

        # Inner Exclusive Switch
        if lock_until:
            if not force:
                confirm_enable_restaking_lock(emitter, staking_address=staking_address, release_period=lock_until)
            receipt = STAKEHOLDER.enable_restaking_lock(release_period=lock_until)
            emitter.echo(f'Successfully enabled re-staking lock for {staking_address} until {lock_until}',
                         color='green', verbosity=1)
        elif enable:
            if not force:
                confirm_enable_restaking(emitter, staking_address=staking_address)
            receipt = STAKEHOLDER.enable_restaking()
            emitter.echo(f'Successfully enabled re-staking for {staking_address}', color='green', verbosity=1)
        else:
            if not force:
                click.confirm(f"Confirm disable re-staking for staker {staking_address}?", abort=True)
            receipt = STAKEHOLDER.disable_restaking()
            emitter.echo(f'Successfully disabled re-staking for {staking_address}', color='green', verbosity=1)

        paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=blockchain.client.chain_name)
        return  # Exit

    elif action == 'divide':
        """Divide an existing stake by specifying the new target value and end period"""

        if staking_address and index is not None:  # 0 is valid.
            current_stake = STAKEHOLDER.stakes[index]
        else:
            current_stake = select_stake(stakeholder=STAKEHOLDER, emitter=emitter)

        #
        # Stage Stake
        #

        # Value
        if not value:
            value = click.prompt(f"Enter target value (must be less than or equal to {str(current_stake.value)})",
                                 type=stake_value_range)
        value = NU(value, 'NU')

        # Duration
        if not lock_periods:
            extension = click.prompt("Enter number of periods to extend", type=stake_extension_range)
        else:
            extension = lock_periods

        if not force:
            painting.paint_staged_stake_division(emitter=emitter,
                                                 stakeholder=STAKEHOLDER,
                                                 original_stake=current_stake,
                                                 target_value=value,
                                                 extension=extension)
            click.confirm("Is this correct?", abort=True)

        # Execute
        password = None
        if not hw_wallet and not blockchain.client.is_local:
            password = get_client_password(checksum_address=current_stake.staker_address)

        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('Successfully divided stake', color='green', verbosity=1)
        paint_receipt_summary(emitter=emitter,
                              receipt=new_stake.receipt,
                              chain_name=blockchain.client.chain_name)

        # Show the resulting stake list
        painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes)
        return  # Exit

    elif action == 'collect-reward':
        """Withdraw staking reward to the specified wallet address"""

        password = None
        if not hw_wallet and not blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)

        if not staking_reward and not policy_reward:
            raise click.BadArgumentUsage(f"Either --staking-reward or --policy-reward must be True to collect rewards.")

        STAKEHOLDER.assimilate(checksum_address=staking_address, password=password)
        if staking_reward:
            # Note: Sending staking / inflation rewards to another account is not allowed.
            staking_receipt = STAKEHOLDER.collect_staking_reward()
            paint_receipt_summary(receipt=staking_receipt,
                                  chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name,
                                  emitter=emitter)

        if policy_reward:
            policy_receipt = STAKEHOLDER.collect_policy_reward(collector_address=withdraw_address)
            paint_receipt_summary(receipt=policy_receipt,
                                  chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name,
                                  emitter=emitter)

        return  # Exit

    # Catch-All for unknown actions
    else:
        ctx = click.get_current_context()
        raise click.UsageError(message=f"Unknown action '{action}'.", ctx=ctx)
Esempio n. 4
0
def stake(
    click_config,
    action,
    config_root,
    config_file,

    # Mode
    force,
    offline,
    hw_wallet,

    # Blockchain
    poa,
    registry_filepath,
    provider_uri,
    sync,

    # Stake
    staking_address,
    worker_address,
    withdraw_address,
    value,
    duration,
    index,
    policy_reward,
    staking_reward,
) -> None:
    """
    Manage stakes and other staker-related operations.

    \b
    Actions
    -------------------------------------------------
    new-stakeholder  Create a new stakeholder configuration
    list             List active stakes for current stakeholder
    accounts         Show ETH and NU balances for stakeholder's accounts
    sync             Synchronize stake data with on-chain information
    set-worker       Bound a worker to a staker
    detach-worker    Detach worker currently bound to a staker
    init             Create a new stake
    divide           Create a new stake from part of an existing one
    collect-reward   Withdraw staking reward

    """

    # Banner
    emitter = click_config.emitter
    emitter.clear()
    emitter.banner(NU_BANNER)

    if action == 'new-stakeholder':

        if not provider_uri:
            raise click.BadOptionUsage(
                option_name='--provider',
                message="--provider is required to create a new stakeholder.")

        registry = None
        fetch_registry = True
        if registry_filepath:
            registry = EthereumContractRegistry(
                registry_filepath=registry_filepath)
            fetch_registry = False
        blockchain = BlockchainInterface(provider_uri=provider_uri,
                                         registry=registry,
                                         poa=poa)
        blockchain.connect(sync_now=sync, fetch_registry=fetch_registry)

        new_stakeholder = StakeHolder(config_root=config_root,
                                      offline_mode=offline,
                                      blockchain=blockchain)

        filepath = new_stakeholder.to_configuration_file(override=force)
        emitter.echo(f"Wrote new stakeholder configuration to {filepath}",
                     color='green')
        return  # Exit

    #
    # Make Staker
    #

    STAKEHOLDER = StakeHolder.from_configuration_file(
        filepath=config_file,
        provider_uri=provider_uri,
        registry_filepath=registry_filepath,
        offline=offline,
        sync_now=sync)
    #
    # Eager Actions
    #

    if action == 'list':
        if not STAKEHOLDER.stakes:
            emitter.echo(f"There are no active stakes")
        else:
            painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes)
        return

    elif action == 'accounts':
        for address, balances in STAKEHOLDER.account_balances.items():
            emitter.echo(
                f"{address} | {Web3.fromWei(balances['ETH'], 'ether')} ETH | {NU.from_nunits(balances['NU'])}"
            )
        return  # Exit

    elif action == 'sync':
        emitter.echo("Reading on-chain stake data...")
        STAKEHOLDER.read_onchain_stakes()
        STAKEHOLDER.to_configuration_file(override=True)
        emitter.echo("OK!", color='green')
        return  # Exit

    elif action in ('set-worker', 'detach-worker'):

        if not staking_address:
            staking_address = select_stake(stakeholder=STAKEHOLDER,
                                           emitter=emitter).owner_address

        if action == 'set-worker':
            if not worker_address:
                worker_address = click.prompt("Enter worker address",
                                              type=EIP55_CHECKSUM_ADDRESS)
        elif action == 'detach-worker':
            if worker_address:
                raise click.BadOptionUsage(
                    message=
                    "detach-worker cannot be used together with --worker-address option"
                )
            worker_address = BlockchainInterface.NULL_ADDRESS

        password = None
        if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)
        receipt = STAKEHOLDER.set_worker(staker_address=staking_address,
                                         password=password,
                                         worker_address=worker_address)

        emitter.echo(f"OK | Receipt: {receipt['transactionHash'].hex()}",
                     color='green')
        return  # Exit

    elif action == 'init':
        """Initialize a new stake"""

        #
        # Get Staking Account
        #

        password = None
        if not staking_address:
            staking_address = select_client_account(
                blockchain=STAKEHOLDER.blockchain,
                prompt="Select staking account",
                emitter=emitter)

        if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local:
            password = click.prompt(
                f"Enter password to unlock {staking_address}",
                hide_input=True,
                confirmation_prompt=False)
        #
        # Stage Stake
        #

        if not value:
            min_locked = STAKEHOLDER.economics.minimum_allowed_locked
            value = click.prompt(
                f"Enter stake value in NU",
                type=STAKE_VALUE,
                default=NU.from_nunits(min_locked).to_tokens())
        value = NU.from_tokens(value)

        if not duration:
            prompt = f"Enter stake duration ({STAKEHOLDER.economics.minimum_locked_periods} periods minimum)"
            duration = click.prompt(prompt, type=STAKE_DURATION)

        start_period = STAKEHOLDER.staking_agent.get_current_period()
        end_period = start_period + duration

        #
        # Review
        #

        if not force:
            painting.paint_staged_stake(emitter=emitter,
                                        stakeholder=STAKEHOLDER,
                                        staking_address=staking_address,
                                        stake_value=value,
                                        duration=duration,
                                        start_period=start_period,
                                        end_period=end_period)

            confirm_staged_stake(staker_address=staking_address,
                                 value=value,
                                 duration=duration)

        # Last chance to bail
        click.confirm("Publish staged stake to the blockchain?", abort=True)

        # Execute
        new_stake = STAKEHOLDER.initialize_stake(
            amount=value,
            duration=duration,
            checksum_address=staking_address,
            password=password)

        painting.paint_staking_confirmation(
            emitter=emitter,
            ursula=STAKEHOLDER,
            transactions=new_stake.transactions)
        return  # Exit

    elif action == 'divide':
        """Divide an existing stake by specifying the new target value and end period"""

        if staking_address and index is not None:
            staker = STAKEHOLDER.get_active_staker(address=staking_address)
            current_stake = staker.stakes[index]
        else:
            current_stake = select_stake(stakeholder=STAKEHOLDER,
                                         emitter=emitter)

        #
        # Stage Stake
        #

        # Value
        if not value:
            value = click.prompt(
                f"Enter target value (must be less than or equal to {str(current_stake.value)})",
                type=STAKE_VALUE)
        value = NU(value, 'NU')

        # Duration
        if not duration:
            extension = click.prompt("Enter number of periods to extend",
                                     type=STAKE_EXTENSION)
        else:
            extension = duration

        if not force:
            painting.paint_staged_stake_division(emitter=emitter,
                                                 stakeholder=STAKEHOLDER,
                                                 original_stake=current_stake,
                                                 target_value=value,
                                                 extension=extension)
            click.confirm("Is this correct?", abort=True)

        # Execute
        password = None
        if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local:
            password = get_client_password(
                checksum_address=current_stake.owner_address)
        modified_stake, new_stake = STAKEHOLDER.divide_stake(
            address=current_stake.owner_address,
            index=current_stake.index,
            value=value,
            duration=extension,
            password=password)
        emitter.echo('Successfully divided stake', color='green', verbosity=1)
        emitter.echo(f'Receipt ........... {new_stake.receipt}', verbosity=1)

        # Show the resulting stake list
        painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes)
        return  # Exit

    elif action == 'collect-reward':
        """Withdraw staking reward to the specified wallet address"""
        password = None
        if not hw_wallet and not STAKEHOLDER.blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)
        STAKEHOLDER.collect_rewards(staker_address=staking_address,
                                    withdraw_address=withdraw_address,
                                    password=password,
                                    staking=staking_reward,
                                    policy=policy_reward)

    else:
        ctx = click.get_current_context()
        click.UsageError(message=f"Unknown action '{action}'.", ctx=ctx).show()
    return  # Exit
Esempio n. 5
0
def divide(
        click_config,

        # Stake Options
        poa,
        light,
        registry_filepath,
        config_file,
        provider_uri,
        staking_address,
        hw_wallet,
        beneficiary_address,
        allocation_filepath,

        # Other
        force,
        value,
        lock_periods,
        index):
    """
    Create a new stake from part of an existing one.
    """

    ### Setup ###
    emitter = _setup_emitter(click_config)

    STAKEHOLDER, blockchain = _create_stakeholder(
        config_file,
        provider_uri,
        poa,
        light,
        registry_filepath,
        staking_address,
        beneficiary_address=beneficiary_address,
        allocation_filepath=allocation_filepath)
    #############

    client_account, staking_address = handle_client_account_for_staking(
        emitter=emitter,
        stakeholder=STAKEHOLDER,
        staking_address=staking_address,
        individual_allocation=STAKEHOLDER.individual_allocation,
        force=force)

    economics = STAKEHOLDER.economics

    # 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)
    stake_extension_range = click.IntRange(
        min=1, max=economics.maximum_allowed_locked, clamp=False)

    if staking_address and index is not None:  # 0 is valid.
        STAKEHOLDER.stakes = StakeList(registry=STAKEHOLDER.registry,
                                       checksum_address=staking_address)
        STAKEHOLDER.stakes.refresh()
        current_stake = STAKEHOLDER.stakes[index]
    else:
        current_stake = select_stake(stakeholder=STAKEHOLDER, emitter=emitter)

    #
    # Stage Stake
    #

    # Value
    if not value:
        value = click.prompt(
            f"Enter target value (must be less than or equal to {str(current_stake.value)})",
            type=stake_value_range)
    value = NU(value, 'NU')

    # Duration
    if not lock_periods:
        extension = click.prompt("Enter number of periods to extend",
                                 type=stake_extension_range)
    else:
        extension = lock_periods

    if not force:
        painting.paint_staged_stake_division(emitter=emitter,
                                             stakeholder=STAKEHOLDER,
                                             original_stake=current_stake,
                                             target_value=value,
                                             extension=extension)
        click.confirm("Is this correct?", abort=True)

    # Execute
    password = None
    if not hw_wallet and not blockchain.client.is_local:
        password = get_client_password(
            checksum_address=current_stake.staker_address)

    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('Successfully divided stake', color='green', verbosity=1)
    paint_receipt_summary(emitter=emitter,
                          receipt=new_stake.receipt,
                          chain_name=blockchain.client.chain_name)

    # Show the resulting stake list
    painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes)