mirror of https://github.com/nucypher/nucypher.git
Extracts staking CLI to 'nucypher stake' entry point
parent
7b16196765
commit
4803831b18
|
@ -21,7 +21,6 @@ import click
|
|||
from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION
|
||||
from twisted.internet import stdio
|
||||
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.characters.banners import URSULA_BANNER
|
||||
from nucypher.cli import actions, painting
|
||||
from nucypher.cli.actions import get_password
|
||||
|
@ -30,10 +29,8 @@ from nucypher.cli.processes import UrsulaCommandProtocol
|
|||
from nucypher.cli.types import (
|
||||
EIP55_CHECKSUM_ADDRESS,
|
||||
NETWORK_PORT,
|
||||
EXISTING_READABLE_FILE,
|
||||
STAKE_DURATION,
|
||||
STAKE_EXTENSION,
|
||||
STAKE_VALUE)
|
||||
EXISTING_READABLE_FILE
|
||||
)
|
||||
from nucypher.config.characters import UrsulaConfiguration
|
||||
from nucypher.utilities.sandbox.constants import (
|
||||
TEMPORARY_DOMAIN,
|
||||
|
@ -55,7 +52,6 @@ from nucypher.utilities.sandbox.constants import (
|
|||
@click.option('--rest-port', help="The host port to run Ursula network services on", type=NETWORK_PORT)
|
||||
@click.option('--db-filepath', help="The database filepath to connect to", type=click.STRING)
|
||||
@click.option('--checksum-address', help="Run with a specified account", type=EIP55_CHECKSUM_ADDRESS)
|
||||
@click.option('--withdraw-address', help="Send reward collection to an alternate address", type=EIP55_CHECKSUM_ADDRESS)
|
||||
@click.option('--federated-only', '-F', help="Connect only to federated nodes", is_flag=True, default=None)
|
||||
@click.option('--interactive', '-I', help="Launch command interface after connecting to seednodes.", is_flag=True, default=False)
|
||||
@click.option('--config-root', help="Custom configuration directory", type=click.Path())
|
||||
|
@ -67,11 +63,6 @@ from nucypher.utilities.sandbox.constants import (
|
|||
@click.option('--provider-uri', help="Blockchain provider's URI", type=click.STRING)
|
||||
@click.option('--no-registry', help="Skip importing the default contract registry", is_flag=True)
|
||||
@click.option('--registry-filepath', help="Custom contract registry filepath", type=EXISTING_READABLE_FILE)
|
||||
@click.option('--value', help="Token value of stake", type=click.INT)
|
||||
@click.option('--duration', help="Period duration of stake", type=click.INT)
|
||||
@click.option('--index', help="A specific stake index to resume", type=click.INT)
|
||||
@click.option('--list', '-l', 'list_', help="List all blockchain stakes", is_flag=True)
|
||||
@click.option('--divide', '-d', help="Divide an existing stake into sub-stakes.", is_flag=True)
|
||||
@nucypher_click_config
|
||||
def ursula(click_config,
|
||||
action,
|
||||
|
@ -87,24 +78,16 @@ def ursula(click_config,
|
|||
rest_port,
|
||||
db_filepath,
|
||||
checksum_address,
|
||||
withdraw_address,
|
||||
federated_only,
|
||||
poa,
|
||||
sync,
|
||||
config_root,
|
||||
config_file,
|
||||
provider_uri,
|
||||
geth,
|
||||
no_registry,
|
||||
registry_filepath,
|
||||
value,
|
||||
duration,
|
||||
index,
|
||||
list_,
|
||||
divide,
|
||||
sync,
|
||||
device,
|
||||
interactive,
|
||||
|
||||
) -> None:
|
||||
"""
|
||||
Manage and run an "Ursula" PRE node.
|
||||
|
@ -333,116 +316,6 @@ def ursula(click_config,
|
|||
actions.forget(configuration=ursula_config)
|
||||
return
|
||||
|
||||
elif action == 'stake':
|
||||
|
||||
# List Only
|
||||
if list_:
|
||||
if not URSULA.stakes:
|
||||
click.echo(f"There are no active stakes for {URSULA.checksum_address}")
|
||||
else:
|
||||
painting.paint_stakes(stakes=URSULA.stakes)
|
||||
return
|
||||
|
||||
# Divide Only
|
||||
if divide:
|
||||
"""Divide an existing stake by specifying the new target value and end period"""
|
||||
|
||||
# Validate
|
||||
if not URSULA.stakes:
|
||||
click.echo(f"There are no active stakes for {URSULA.checksum_address}")
|
||||
return
|
||||
|
||||
# Selection
|
||||
if index is None:
|
||||
painting.paint_stakes(stakes=URSULA.stakes)
|
||||
index = click.prompt("Select a stake to divide", type=click.IntRange(min=0, max=len(URSULA.stakes)-1))
|
||||
|
||||
# Lookup the stake
|
||||
current_stake = URSULA.stakes[index]
|
||||
|
||||
# Value
|
||||
if not value:
|
||||
value = click.prompt(f"Enter target value (must be less than {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(ursula=URSULA,
|
||||
original_index=index,
|
||||
original_stake=current_stake,
|
||||
target_value=value,
|
||||
extension=extension)
|
||||
|
||||
click.confirm("Is this correct?", abort=True)
|
||||
|
||||
modified_stake, new_stake = URSULA.divide_stake(stake_index=index,
|
||||
target_value=value,
|
||||
additional_periods=extension)
|
||||
|
||||
if not quiet:
|
||||
click.secho('Successfully divided stake', fg='green')
|
||||
click.secho(f'Transaction Hash ........... {new_stake.receipt}')
|
||||
|
||||
# Show the resulting stake list
|
||||
painting.paint_stakes(stakes=URSULA.stakes)
|
||||
|
||||
return
|
||||
|
||||
# Confirm new stake init
|
||||
if not force:
|
||||
click.confirm("Stage a new stake?", abort=True)
|
||||
|
||||
# Validate balance
|
||||
balance = URSULA.token_balance
|
||||
if balance == 0:
|
||||
click.secho(f"{URSULA.checksum_address} has 0 NU.")
|
||||
raise click.Abort
|
||||
if not quiet:
|
||||
click.echo(f"Current balance: {balance}")
|
||||
|
||||
# Gather stake value
|
||||
if not value:
|
||||
min_locked = NU(URSULA.economics.minimum_allowed_locked, 'NuNit')
|
||||
value = click.prompt(f"Enter stake value", type=STAKE_VALUE, default=min_locked)
|
||||
else:
|
||||
value = NU(int(value), 'NU')
|
||||
|
||||
# Duration
|
||||
if not quiet:
|
||||
message = f"Minimum duration: {URSULA.economics.minimum_allowed_locked} | " \
|
||||
f"Maximum Duration: {URSULA.economics.maximum_allowed_locked}"
|
||||
click.echo(message)
|
||||
if not duration:
|
||||
duration = click.prompt("Enter stake duration in periods (1 Period = 24 Hours)", type=STAKE_DURATION)
|
||||
start_period = URSULA.staking_agent.get_current_period()
|
||||
end_period = start_period + duration
|
||||
|
||||
# Review
|
||||
if not force:
|
||||
painting.paint_staged_stake(ursula=URSULA,
|
||||
stake_value=value,
|
||||
duration=duration,
|
||||
start_period=start_period,
|
||||
end_period=end_period)
|
||||
|
||||
if not dev:
|
||||
actions.confirm_staged_stake(ursula=URSULA, value=value, duration=duration)
|
||||
|
||||
# Last chance to bail
|
||||
if not force:
|
||||
click.confirm("Publish staged stake to the blockchain?", abort=True)
|
||||
|
||||
stake = URSULA.initialize_stake(amount=int(value), lock_periods=duration)
|
||||
# TODO temporary fix to not break backward compatibility
|
||||
URSULA.set_worker(worker_address=URSULA.checksum_address)
|
||||
painting.paint_staking_confirmation(ursula=URSULA, transactions=stake.transactions)
|
||||
return
|
||||
|
||||
elif action == 'confirm-activity':
|
||||
if not URSULA.stakes:
|
||||
click.secho("There are no active stakes for {}".format(URSULA.checksum_address))
|
||||
|
@ -450,14 +323,5 @@ def ursula(click_config,
|
|||
URSULA.staking_agent.confirm_activity(node_address=URSULA.checksum_address)
|
||||
return
|
||||
|
||||
elif action == 'collect-reward':
|
||||
"""Withdraw staking reward to the specified wallet address"""
|
||||
if not force:
|
||||
click.confirm(f"Send {URSULA.calculate_reward()} to {URSULA.checksum_address}?")
|
||||
inflation_reward = URSULA.calculate_reward()
|
||||
if inflation_reward:
|
||||
URSULA.collect_staking_reward()
|
||||
URSULA.collect_policy_reward(collector_address=withdraw_address or checksum_address)
|
||||
|
||||
else:
|
||||
raise click.BadArgumentUsage("No such argument {}".format(action))
|
||||
|
|
|
@ -23,6 +23,8 @@ from twisted.logger import globalLogPublisher
|
|||
from nucypher.characters.banners import NUCYPHER_BANNER
|
||||
from nucypher.characters.control.emitters import StdoutEmitter, JSONRPCStdoutEmitter
|
||||
from nucypher.cli import status
|
||||
from nucypher.cli import status, stake
|
||||
from nucypher.cli.actions import destroy_configuration_root
|
||||
from nucypher.cli.characters import moe, ursula, alice, bob, enrico, felix
|
||||
from nucypher.cli.config import nucypher_click_config, NucypherClickConfig
|
||||
from nucypher.cli.painting import echo_version
|
||||
|
@ -136,7 +138,10 @@ Inversely, commenting out an entry point here will disable it.
|
|||
ENTRY_POINTS = (
|
||||
|
||||
# Utility Sub-Commands
|
||||
# Utility Commands
|
||||
status.status, # Network Status
|
||||
stake.stake, # Stake Management
|
||||
# device.device, # TODO: nucypher device # Hardware Wallet Management
|
||||
|
||||
# Characters
|
||||
alice.alice, # Author of Policies
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
|
||||
import click
|
||||
|
||||
from nucypher.blockchain.eth.actors import Staker
|
||||
from nucypher.blockchain.eth.chains import Blockchain
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.characters.banners import NU_BANNER
|
||||
from nucypher.cli import painting
|
||||
from nucypher.cli.actions import confirm_staged_stake
|
||||
from nucypher.cli.types import (
|
||||
EIP55_CHECKSUM_ADDRESS,
|
||||
STAKE_VALUE, STAKE_DURATION, STAKE_EXTENSION)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument('action')
|
||||
@click.option('--force', help="Don't ask for confirmation", is_flag=True)
|
||||
@click.option('--quiet', '-Q', help="Disable logging", is_flag=True)
|
||||
@click.option('--poa', help="Inject POA middleware", is_flag=True, default=None)
|
||||
@click.option('--provider-uri', help="Blockchain provider's URI", type=click.STRING)
|
||||
@click.option('--staking-address', help="Address to stake NU ERC20 tokens", type=EIP55_CHECKSUM_ADDRESS)
|
||||
@click.option('--worker-address', help="Run with a specified account", type=EIP55_CHECKSUM_ADDRESS)
|
||||
@click.option('--withdraw-address', help="Send reward collection to an alternate address", type=EIP55_CHECKSUM_ADDRESS)
|
||||
@click.option('--value', help="Token value of stake", type=click.INT)
|
||||
@click.option('--duration', help="Period duration of stake", type=click.INT)
|
||||
@click.option('--index', help="A specific stake index to resume", type=click.INT)
|
||||
def stake(action,
|
||||
|
||||
# Mode
|
||||
force,
|
||||
quiet,
|
||||
|
||||
# Blockchain
|
||||
poa,
|
||||
provider_uri,
|
||||
|
||||
# Stake
|
||||
staking_address,
|
||||
worker_address,
|
||||
withdraw_address,
|
||||
value,
|
||||
duration,
|
||||
index
|
||||
|
||||
) -> None:
|
||||
|
||||
# Banner
|
||||
click.clear()
|
||||
if not quiet:
|
||||
click.secho(NU_BANNER)
|
||||
|
||||
#
|
||||
# Make Staker
|
||||
#
|
||||
|
||||
blockchain = Blockchain.connect(provider_uri=provider_uri, poa=poa)
|
||||
|
||||
STAKER = Staker(is_me=True,
|
||||
checksum_address=staking_address,
|
||||
blockchain=blockchain)
|
||||
|
||||
if action == 'list':
|
||||
if not STAKER.stakes:
|
||||
click.echo(f"There are no active stakes for {STAKER.checksum_public_address}")
|
||||
else:
|
||||
painting.paint_stakes(stakes=STAKER.stakes)
|
||||
return
|
||||
|
||||
elif action == 'set-worker':
|
||||
STAKER.set_worker(worker_address=worker_address)
|
||||
pass
|
||||
|
||||
elif action == 'init':
|
||||
|
||||
# Confirm new stake init
|
||||
if not force:
|
||||
click.confirm("Stage a new stake?", abort=True)
|
||||
|
||||
# Validate balance
|
||||
balance = STAKER.token_balance
|
||||
if balance == 0:
|
||||
click.secho(f"{STAKER.checksum_public_address} has 0 NU.")
|
||||
raise click.Abort
|
||||
|
||||
if not quiet:
|
||||
click.echo(f"Current balance: {balance}")
|
||||
|
||||
# Gather stake value
|
||||
if not value:
|
||||
min_locked = NU(STAKER.economics.minimum_allowed_locked, 'NuNit')
|
||||
value = click.prompt(f"Enter stake value", type=STAKE_VALUE, default=min_locked)
|
||||
else:
|
||||
value = NU(int(value), 'NU')
|
||||
|
||||
# Duration
|
||||
if not quiet:
|
||||
message = f"Minimum duration: {STAKER.economics.minimum_allowed_locked} | " \
|
||||
f"Maximum Duration: {STAKER.economics.maximum_allowed_locked}"
|
||||
click.echo(message)
|
||||
|
||||
if not duration:
|
||||
duration = click.prompt("Enter stake duration in periods (1 Period = 24 Hours)", type=STAKE_DURATION)
|
||||
|
||||
start_period = STAKER.staking_agent.get_current_period()
|
||||
end_period = start_period + duration
|
||||
|
||||
# Review
|
||||
if not force:
|
||||
painting.paint_staged_stake(ursula=STAKER,
|
||||
stake_value=value,
|
||||
duration=duration,
|
||||
start_period=start_period,
|
||||
end_period=end_period)
|
||||
|
||||
confirm_staged_stake(ursula=STAKER, value=value, duration=duration)
|
||||
|
||||
# Last chance to bail
|
||||
if not force:
|
||||
click.confirm("Publish staged stake to the blockchain?", abort=True)
|
||||
|
||||
new_stake = STAKER.initialize_stake(amount=int(value), lock_periods=duration)
|
||||
painting.paint_staking_confirmation(ursula=STAKER, transactions=new_stake.transactions)
|
||||
|
||||
return
|
||||
|
||||
elif action == 'divide':
|
||||
"""Divide an existing stake by specifying the new target value and end period"""
|
||||
|
||||
# Validate
|
||||
if not STAKER.stakes:
|
||||
click.echo(f"There are no active stakes for {STAKER.checksum_public_address}")
|
||||
return
|
||||
|
||||
# Selection
|
||||
if index is None:
|
||||
painting.paint_stakes(stakes=STAKER.stakes)
|
||||
index = click.prompt("Select a stake to divide", type=click.IntRange(min=0, max=len(STAKER.stakes)-1))
|
||||
|
||||
# Lookup the stake
|
||||
current_stake = STAKER.stakes[index]
|
||||
|
||||
# Value
|
||||
if not value:
|
||||
value = click.prompt(f"Enter target value (must be less than {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(ursula=STAKER,
|
||||
original_index=index,
|
||||
original_stake=current_stake,
|
||||
target_value=value,
|
||||
extension=extension)
|
||||
|
||||
click.confirm("Is this correct?", abort=True)
|
||||
|
||||
modified_stake, new_stake = STAKER.divide_stake(stake_index=index,
|
||||
target_value=value,
|
||||
additional_periods=extension)
|
||||
|
||||
if not quiet:
|
||||
click.secho('Successfully divided stake', fg='green')
|
||||
click.secho(f'Transaction Hash ........... {new_stake.receipt}')
|
||||
|
||||
# Show the resulting stake list
|
||||
painting.paint_stakes(stakes=STAKER.stakes)
|
||||
|
||||
return
|
||||
|
||||
elif action == 'collect-reward':
|
||||
"""Withdraw staking reward to the specified wallet address"""
|
||||
if not force:
|
||||
click.confirm(f"Send {STAKER.calculate_reward()} to {STAKER.checksum_public_address}?")
|
||||
|
||||
STAKER.collect_policy_reward(collector_address=withdraw_address)
|
||||
STAKER.collect_staking_reward()
|
Loading…
Reference in New Issue