-
Notifications
You must be signed in to change notification settings - Fork 188
Add support for wallet
send
, sendmany
, sign
, open
, and close
#726
Changes from 19 commits
ba231b1
1529505
dba3ffb
7d94d3c
191b28a
7d14b07
dd8030f
d9f5102
ad864fc
309c594
0a12c55
de79875
54d5166
86396e0
fe95dee
d288e4d
d654416
01076dc
38e77fb
f6db1fe
5d766cc
1944217
1a390d2
6755c6a
809faab
9f2d7f1
18d5ec0
72bf6cb
b2958e6
6e1e95c
faaa972
963baee
f53f0a8
041ad14
9c3b679
e7c9914
d870595
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,16 +13,73 @@ | |
import json | ||
from prompt_toolkit import prompt | ||
import traceback | ||
from neo.Prompt.PromptData import PromptData | ||
from neo.Prompt.CommandBase import CommandBase, CommandDesc, ParameterDesc | ||
from logzero import logger | ||
|
||
|
||
class CommandWalletSend(CommandBase): | ||
|
||
def __init__(self): | ||
super().__init__() | ||
|
||
def execute(self, arguments): | ||
framework = construct_send_basic(PromptData.Wallet, arguments) | ||
if type(framework) is list: | ||
process_transaction(PromptData.Wallet, contract_tx=framework[0], scripthash_from=framework[1], fee=framework[2], owners=framework[3], user_tx_attributes=framework[4]) | ||
|
||
def command_desc(self): | ||
p1 = ParameterDesc('assetId or name', 'the asset you wish to send') | ||
p2 = ParameterDesc('address', 'the NEO address you will send to') | ||
p3 = ParameterDesc('amount', 'the amount of the asset you wish to send') | ||
p4 = ParameterDesc('--from-addr={addr}', 'specify the NEO address you wish to send from', optional=True) | ||
p5 = ParameterDesc('--fee={priority_fee}', 'attach a fee to give your tx priority (> 0.001)', optional=True) | ||
p6 = ParameterDesc('--owners=[{addr}, ...]', 'specify tx owners', optional=True) | ||
p7 = ParameterDesc('--tx-attr=[{"usage": <value>,"data":"<remark>"}, ...]', 'specify unique tx attributes', optional=True) | ||
params = [p1, p2, p3, p4, p5, p6, p7] | ||
return CommandDesc('send', 'send an asset', params=params) | ||
|
||
|
||
class CommandWalletSendMany(CommandBase): | ||
|
||
def __init__(self): | ||
super().__init__() | ||
|
||
def execute(self, arguments): | ||
framework = construct_send_many(PromptData.Wallet, arguments) | ||
if type(framework) is list: | ||
process_transaction(PromptData.Wallet, contract_tx=framework[0], scripthash_from=framework[1], scripthash_change=framework[2], fee=framework[3], owners=framework[4], user_tx_attributes=framework[5]) | ||
|
||
def command_desc(self): | ||
p1 = ParameterDesc('number of outgoing tx', 'the number of tx you wish to send') | ||
p2 = ParameterDesc('--change-addr={addr}', 'specify the change address', optional=True) | ||
p3 = ParameterDesc('--from-addr={addr}', 'specify the NEO address you wish to send from', optional=True) | ||
p4 = ParameterDesc('--fee={priority_fee}', 'attach a fee to give your tx priority (> 0.001)', optional=True) | ||
p5 = ParameterDesc('--owners=[{addr}, ...]', 'specify tx owners', optional=True) | ||
p6 = ParameterDesc('--tx-attr=[{"usage": <value>,"data":"<remark>"}, ...]', 'specify unique tx attributes', optional=True) | ||
params = [p1, p2, p3, p4, p5, p6] | ||
return CommandDesc('sendmany', 'send multiple contract transactions', params=params) | ||
|
||
|
||
class CommandWalletSign(CommandBase): | ||
|
||
def __init__(self): | ||
super().__init__() | ||
|
||
def execute(self, arguments): | ||
jsn = get_arg(arguments) | ||
parse_and_sign(PromptData.Wallet, jsn) | ||
|
||
def command_desc(self): | ||
p1 = ParameterDesc('jsn', 'transaction in JSON format') | ||
params = [p1] | ||
return CommandDesc('sign', 'sign multi-sig tx', params=params) | ||
|
||
|
||
def construct_send_basic(wallet, arguments): | ||
if not wallet: | ||
print("please open a wallet") | ||
return False | ||
if len(arguments) < 3: | ||
print("Not enough arguments") | ||
return False | ||
return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @LysanderGG There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see #716 (comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree for When I see @ixje any thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm indifferent when it comes to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The add a few more words regarding return values; at some point I expect to start applying gradual typing. We've had past cases where functions were returning There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the answer, I won't make this as a blocker for this PR, so @jseagrave21 as you want 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like to leave it this way so the format is consistent with #716. Consistency is what matters most to me so people will be able to follow our example throughout the code. |
||
|
||
arguments, from_address = get_from_addr(arguments) | ||
arguments, priority_fee = get_fee(arguments) | ||
|
@@ -35,19 +92,19 @@ def construct_send_basic(wallet, arguments): | |
assetId = get_asset_id(wallet, to_send) | ||
if assetId is None: | ||
print("Asset id not found") | ||
return False | ||
return | ||
|
||
scripthash_to = lookup_addr_str(wallet, address_to) | ||
if scripthash_to is None: | ||
logger.debug("invalid address") | ||
return False | ||
return | ||
|
||
scripthash_from = None | ||
if from_address is not None: | ||
scripthash_from = lookup_addr_str(wallet, from_address) | ||
if scripthash_from is None: | ||
logger.debug("invalid address") | ||
return False | ||
return | ||
|
||
# if this is a token, we will use a different | ||
# transfer mechanism | ||
|
@@ -57,38 +114,35 @@ def construct_send_basic(wallet, arguments): | |
f8amount = get_asset_amount(amount, assetId) | ||
if f8amount is False: | ||
logger.debug("invalid amount") | ||
return False | ||
return | ||
if float(amount) == 0: | ||
print("amount cannot be 0") | ||
return False | ||
return | ||
|
||
fee = Fixed8.Zero() | ||
if priority_fee is not None: | ||
fee = priority_fee | ||
if fee is False: | ||
logger.debug("invalid fee") | ||
return False | ||
return | ||
|
||
output = TransactionOutput(AssetId=assetId, Value=f8amount, script_hash=scripthash_to) | ||
contract_tx = ContractTransaction(outputs=[output]) | ||
return [contract_tx, scripthash_from, fee, owners, user_tx_attributes] | ||
|
||
|
||
def construct_send_many(wallet, arguments): | ||
if not wallet: | ||
print("please open a wallet") | ||
return False | ||
if len(arguments) is 0: | ||
print("Not enough arguments") | ||
return False | ||
return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should return something here too (and other early returns). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
outgoing = get_arg(arguments, convert_to_int=True) | ||
if outgoing is None: | ||
print("invalid outgoing number") | ||
return False | ||
return | ||
if outgoing < 1: | ||
print("outgoing number must be >= 1") | ||
return False | ||
return | ||
|
||
arguments, from_address = get_from_addr(arguments) | ||
arguments, change_address = get_change_addr(arguments) | ||
|
@@ -103,23 +157,23 @@ def construct_send_many(wallet, arguments): | |
assetId = get_asset_id(wallet, to_send) | ||
if assetId is None: | ||
print("Asset id not found") | ||
return False | ||
return | ||
if type(assetId) is NEP5Token: | ||
print('Sendmany does not support NEP5 tokens') | ||
return False | ||
return | ||
address_to = prompt("Address to: ") | ||
scripthash_to = lookup_addr_str(wallet, address_to) | ||
if scripthash_to is None: | ||
logger.debug("invalid address") | ||
return False | ||
return | ||
amount = prompt("Amount to send: ") | ||
f8amount = get_asset_amount(amount, assetId) | ||
if f8amount is False: | ||
logger.debug("invalid amount") | ||
return False | ||
return | ||
if float(amount) == 0: | ||
print("amount cannot be 0") | ||
return False | ||
return | ||
tx_output = TransactionOutput(AssetId=assetId, Value=f8amount, script_hash=scripthash_to) | ||
output.append(tx_output) | ||
contract_tx = ContractTransaction(outputs=output) | ||
|
@@ -130,22 +184,22 @@ def construct_send_many(wallet, arguments): | |
scripthash_from = lookup_addr_str(wallet, from_address) | ||
if scripthash_from is None: | ||
logger.debug("invalid address") | ||
return False | ||
return | ||
|
||
scripthash_change = None | ||
|
||
if change_address is not None: | ||
scripthash_change = lookup_addr_str(wallet, change_address) | ||
if scripthash_change is None: | ||
logger.debug("invalid address") | ||
return False | ||
return | ||
|
||
fee = Fixed8.Zero() | ||
if priority_fee is not None: | ||
fee = priority_fee | ||
if fee is False: | ||
logger.debug("invalid fee") | ||
return False | ||
return | ||
|
||
print("sending with fee: %s " % fee.ToString()) | ||
return [contract_tx, scripthash_from, scripthash_change, fee, owners, user_tx_attributes] | ||
|
@@ -160,13 +214,13 @@ def process_transaction(wallet, contract_tx, scripthash_from=None, scripthash_ch | |
|
||
if tx is None: | ||
logger.debug("insufficient funds") | ||
return False | ||
return | ||
|
||
# password prompt | ||
passwd = prompt("[Password]> ", is_password=True) | ||
if not wallet.ValidatePassword(passwd): | ||
print("incorrect password") | ||
return False | ||
return | ||
|
||
standard_contract = wallet.GetStandardAddress() | ||
|
||
|
@@ -216,14 +270,14 @@ def process_transaction(wallet, contract_tx, scripthash_from=None, scripthash_ch | |
else: | ||
print("Transaction initiated, but the signature is incomplete") | ||
print(json.dumps(context.ToJson(), separators=(',', ':'))) | ||
return False | ||
return | ||
|
||
except Exception as e: | ||
print("could not send: %s " % e) | ||
traceback.print_stack() | ||
traceback.print_exc() | ||
|
||
return False | ||
return | ||
|
||
|
||
def parse_and_sign(wallet, jsn): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
from neo.Implementations.Wallets.peewee.Models import Account | ||
from neo.Prompt.CommandBase import CommandBase, CommandDesc, ParameterDesc | ||
from neo.Prompt.PromptData import PromptData | ||
from neo.Prompt.Commands.Send import CommandWalletSend, CommandWalletSendMany, CommandWalletSign | ||
from neo.logging import log_manager | ||
|
||
|
||
|
@@ -30,9 +31,14 @@ def __init__(self): | |
super().__init__() | ||
|
||
self.register_sub_command(CommandWalletCreate()) | ||
self.register_sub_command(CommandWalletOpen()) | ||
self.register_sub_command(CommandWalletClose()) | ||
self.register_sub_command(CommandWalletVerbose(), ['v', '--v']) | ||
self.register_sub_command(CommandWalletMigrate()) | ||
self.register_sub_command(CommandWalletCreateAddress()) | ||
self.register_sub_command(CommandWalletSend()) | ||
self.register_sub_command(CommandWalletSendMany()) | ||
self.register_sub_command(CommandWalletSign()) | ||
|
||
def command_desc(self): | ||
return CommandDesc('wallet', 'manage wallets') | ||
|
@@ -42,7 +48,7 @@ def execute(self, arguments): | |
item = get_arg(arguments) | ||
|
||
# Create and Open must be handled specially. | ||
if item == 'create': | ||
if item in {'create', 'open'}: | ||
self.execute_sub_command(item, arguments[1:]) | ||
return | ||
|
||
|
@@ -55,7 +61,10 @@ def execute(self, arguments): | |
return | ||
|
||
try: | ||
self.execute_sub_command(item, arguments[1:]) | ||
jseagrave21 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if len(arguments) > 1: | ||
self.execute_sub_command(item, arguments[1:]) | ||
else: | ||
self.execute_sub_command(item) | ||
except KeyError: | ||
print(f"Wallet: {item} is an invalid parameter") | ||
|
||
|
@@ -66,6 +75,8 @@ def __init__(self): | |
super().__init__() | ||
|
||
def execute(self, arguments): | ||
if PromptData.Wallet: | ||
CommandWalletClose().execute() | ||
path = get_arg(arguments, 0) | ||
|
||
if path: | ||
|
@@ -109,12 +120,66 @@ def command_desc(self): | |
return CommandDesc('create', 'creates a new NEO wallet address', [p1]) | ||
|
||
|
||
class CommandWalletVerbose(CommandBase): | ||
class CommandWalletOpen(CommandBase): | ||
|
||
def __init__(self): | ||
super().__init__() | ||
|
||
def execute(self, arguments): | ||
if PromptData.Wallet: | ||
CommandWalletClose().execute() | ||
|
||
path = get_arg(arguments, 0) | ||
|
||
if path: | ||
|
||
if not os.path.exists(path): | ||
print("Wallet file not found") | ||
return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should return something here too (and other early returns). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
passwd = prompt("[password]> ", is_password=True) | ||
password_key = to_aes_key(passwd) | ||
|
||
try: | ||
PromptData.Wallet = UserWallet.Open(path, password_key) | ||
|
||
PromptData.Prompt.start_wallet_loop() | ||
print("Opened wallet at %s" % path) | ||
return PromptData.Wallet | ||
except Exception as e: | ||
print("Could not open wallet: %s" % e) | ||
|
||
else: | ||
print("Please specify a path") | ||
|
||
def command_desc(self): | ||
p1 = ParameterDesc('path', 'path to open the wallet file') | ||
return CommandDesc('open', 'opens a NEO wallet', [p1]) | ||
|
||
|
||
class CommandWalletClose(CommandBase): | ||
|
||
def __init__(self): | ||
super().__init__() | ||
|
||
def execute(self): | ||
if PromptData.Wallet: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe the implementation could be moved to a static method or to PromptData. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is why I originally had it as a class method. But changed it after your review. Couldn't we just keep it as a class method? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A static method still required me to instantiate the class There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realize that my message was probably confusing, sorry for that. I was suggesting to keep execute as is (to override I think the following should work: class CommandWalletClose(CommandBase):
...
def execute(self, arguments=None):
CommandWalletClose.close_wallet()
@staticmethod
def close_wallet():
if not PromptData.Wallet:
return False
path = PromptData.Wallet._path
PromptData.Prompt.stop_wallet_loop()
PromptData.Wallet.Close()
PromptData.Wallet = None
print("Closed wallet %s" % path)
return True It should be possible to call it with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @LysanderGG Thank you. I will give it a shot. I will try it your preferred way. |
||
path = PromptData.Wallet._path | ||
PromptData.Prompt.stop_wallet_loop() | ||
PromptData.Wallet.Close() | ||
PromptData.Wallet = None | ||
print("Closed wallet %s" % path) | ||
|
||
def command_desc(self): | ||
return CommandDesc('close', 'closes the open NEO wallet') | ||
|
||
|
||
class CommandWalletVerbose(CommandBase): | ||
|
||
def __init__(self): | ||
super().__init__() | ||
|
||
def execute(self): | ||
print("Wallet %s " % json.dumps(PromptData.Wallet.ToJson(verbose=True), indent=4)) | ||
|
||
def command_desc(self): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, the original signature
execute(self, arguments)
should be respected. None could be given as arguments for commands likeCommandWalletClose
.