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 constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION
|
||||||
from twisted.internet import stdio
|
from twisted.internet import stdio
|
||||||
|
|
||||||
from nucypher.blockchain.eth.token import NU
|
|
||||||
from nucypher.characters.banners import URSULA_BANNER
|
from nucypher.characters.banners import URSULA_BANNER
|
||||||
from nucypher.cli import actions, painting
|
from nucypher.cli import actions, painting
|
||||||
from nucypher.cli.actions import get_password
|
from nucypher.cli.actions import get_password
|
||||||
|
@ -30,10 +29,8 @@ from nucypher.cli.processes import UrsulaCommandProtocol
|
||||||
from nucypher.cli.types import (
|
from nucypher.cli.types import (
|
||||||
EIP55_CHECKSUM_ADDRESS,
|
EIP55_CHECKSUM_ADDRESS,
|
||||||
NETWORK_PORT,
|
NETWORK_PORT,
|
||||||
EXISTING_READABLE_FILE,
|
EXISTING_READABLE_FILE
|
||||||
STAKE_DURATION,
|
)
|
||||||
STAKE_EXTENSION,
|
|
||||||
STAKE_VALUE)
|
|
||||||
from nucypher.config.characters import UrsulaConfiguration
|
from nucypher.config.characters import UrsulaConfiguration
|
||||||
from nucypher.utilities.sandbox.constants import (
|
from nucypher.utilities.sandbox.constants import (
|
||||||
TEMPORARY_DOMAIN,
|
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('--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('--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('--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('--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('--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())
|
@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('--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('--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('--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
|
@nucypher_click_config
|
||||||
def ursula(click_config,
|
def ursula(click_config,
|
||||||
action,
|
action,
|
||||||
|
@ -87,24 +78,16 @@ def ursula(click_config,
|
||||||
rest_port,
|
rest_port,
|
||||||
db_filepath,
|
db_filepath,
|
||||||
checksum_address,
|
checksum_address,
|
||||||
withdraw_address,
|
|
||||||
federated_only,
|
federated_only,
|
||||||
poa,
|
poa,
|
||||||
|
sync,
|
||||||
config_root,
|
config_root,
|
||||||
config_file,
|
config_file,
|
||||||
provider_uri,
|
provider_uri,
|
||||||
geth,
|
geth,
|
||||||
no_registry,
|
no_registry,
|
||||||
registry_filepath,
|
registry_filepath,
|
||||||
value,
|
|
||||||
duration,
|
|
||||||
index,
|
|
||||||
list_,
|
|
||||||
divide,
|
|
||||||
sync,
|
|
||||||
device,
|
|
||||||
interactive,
|
interactive,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Manage and run an "Ursula" PRE node.
|
Manage and run an "Ursula" PRE node.
|
||||||
|
@ -333,116 +316,6 @@ def ursula(click_config,
|
||||||
actions.forget(configuration=ursula_config)
|
actions.forget(configuration=ursula_config)
|
||||||
return
|
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':
|
elif action == 'confirm-activity':
|
||||||
if not URSULA.stakes:
|
if not URSULA.stakes:
|
||||||
click.secho("There are no active stakes for {}".format(URSULA.checksum_address))
|
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)
|
URSULA.staking_agent.confirm_activity(node_address=URSULA.checksum_address)
|
||||||
return
|
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:
|
else:
|
||||||
raise click.BadArgumentUsage("No such argument {}".format(action))
|
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.banners import NUCYPHER_BANNER
|
||||||
from nucypher.characters.control.emitters import StdoutEmitter, JSONRPCStdoutEmitter
|
from nucypher.characters.control.emitters import StdoutEmitter, JSONRPCStdoutEmitter
|
||||||
from nucypher.cli import status
|
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.characters import moe, ursula, alice, bob, enrico, felix
|
||||||
from nucypher.cli.config import nucypher_click_config, NucypherClickConfig
|
from nucypher.cli.config import nucypher_click_config, NucypherClickConfig
|
||||||
from nucypher.cli.painting import echo_version
|
from nucypher.cli.painting import echo_version
|
||||||
|
@ -136,7 +138,10 @@ Inversely, commenting out an entry point here will disable it.
|
||||||
ENTRY_POINTS = (
|
ENTRY_POINTS = (
|
||||||
|
|
||||||
# Utility Sub-Commands
|
# Utility Sub-Commands
|
||||||
|
# Utility Commands
|
||||||
status.status, # Network Status
|
status.status, # Network Status
|
||||||
|
stake.stake, # Stake Management
|
||||||
|
# device.device, # TODO: nucypher device # Hardware Wallet Management
|
||||||
|
|
||||||
# Characters
|
# Characters
|
||||||
alice.alice, # Author of Policies
|
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