mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-29 16:31:29 +00:00
add a --password option, respect --server,--fee,--gap as passed on the command line, and do not ask for them if they were specified. so if all of them are passed, there are no interactive questions asked and one can create wallets automatically via scripts. closes #308 additionally it fixes the bug that the default fee and gap limit were not stored in the new wallet, if entered manually. additionally it fixes the bug that the wallet path was not displayed correctly if a custom wallet path was used.
389 lines
15 KiB
Python
Executable file
389 lines
15 KiB
Python
Executable file
#!/usr/bin/env python
|
|
#
|
|
# Electrum - lightweight Bitcoin client
|
|
# Copyright (C) 2011 thomasv@gitorious
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import re
|
|
import pkgutil
|
|
import sys, os, time, json
|
|
import optparse
|
|
import platform
|
|
from decimal import Decimal
|
|
import traceback
|
|
|
|
try:
|
|
import ecdsa
|
|
except ImportError:
|
|
sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
|
|
|
|
try:
|
|
import aes
|
|
except ImportError:
|
|
sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
|
|
|
|
|
|
is_local = os.path.dirname(os.path.realpath(__file__)) == os.getcwd()
|
|
is_android = 'ANDROID_DATA' in os.environ
|
|
|
|
import __builtin__
|
|
__builtin__.use_local_modules = is_local or is_android
|
|
|
|
# load local module as electrum
|
|
if __builtin__.use_local_modules:
|
|
import imp
|
|
imp.load_module('electrum', *imp.find_module('lib'))
|
|
imp.load_module('electrum_gui', *imp.find_module('gui'))
|
|
|
|
from electrum import *
|
|
|
|
# get password routine
|
|
def prompt_password(prompt, confirm=True):
|
|
import getpass
|
|
if sys.stdin.isatty():
|
|
password = getpass.getpass(prompt)
|
|
if password and confirm:
|
|
password2 = getpass.getpass("Confirm: ")
|
|
if password != password2:
|
|
sys.exit("Error: Passwords do not match.")
|
|
else:
|
|
password = raw_input(prompt)
|
|
if not password:
|
|
password = None
|
|
return password
|
|
|
|
def arg_parser():
|
|
usage = "%prog [options] command"
|
|
parser = optparse.OptionParser(prog=usage, add_help_option = False)
|
|
parser.add_option("-h", "--help", action="callback", callback=print_help_cb, help="show this help text")
|
|
parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
|
|
parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
|
|
parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
|
|
parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
|
|
parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance of listed addresses")
|
|
parser.add_option("-l", "--labels", action="store_true", dest="show_labels", default=False, help="show the labels of listed addresses")
|
|
parser.add_option("-f", "--fee", dest="tx_fee", default=None, help="set tx fee")
|
|
parser.add_option("-F", "--fromaddr", dest="from_addr", default=None, help="set source address for payto/mktx. if it isn't in the wallet, it will ask for the private key unless supplied in the format public_key:private_key. It's not saved in the wallet.")
|
|
parser.add_option("-c", "--changeaddr", dest="change_addr", default=None, help="set the change address for payto/mktx. default is a spare address, or the source address if it's not in the wallet")
|
|
parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
|
|
parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
|
|
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
|
|
parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
|
|
parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
|
|
parser.add_option("-u", "--usb", dest="bitkey", action="store_true", help="Turn on support for hardware wallets (EXPERIMENTAL)")
|
|
parser.add_option("-G", "--gap", dest="gap_limit", default=None, help="gap limit")
|
|
parser.add_option("-W", "--password", dest="password", default=None, help="set password for usage with commands (currently only implemented for create command, do not use it for longrunning gui session since the password is visible in /proc)")
|
|
return parser
|
|
|
|
def print_help(parser):
|
|
parser.print_help()
|
|
print_msg("Type 'electrum help <command>' to see the help for a specific command")
|
|
print_msg("Type 'electrum --help' to see the list of options")
|
|
run_command('help')
|
|
exit(1)
|
|
|
|
def print_help_cb(self, opt, value, parser):
|
|
print_help(parser)
|
|
|
|
def run_command(cmd, password = None, args = []):
|
|
cmd_runner = Commands(wallet, network)
|
|
func = eval('cmd_runner.' + cmd)
|
|
cmd_runner.password = password
|
|
try:
|
|
result = func(*args[1:])
|
|
except BaseException, e:
|
|
import traceback
|
|
traceback.print_exc(file=sys.stdout)
|
|
sys.exit(1)
|
|
|
|
if type(result) == str:
|
|
util.print_msg(result)
|
|
elif result is not None:
|
|
util.print_json(result)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
parser = arg_parser()
|
|
options, args = parser.parse_args()
|
|
if options.portable and options.wallet_path is None:
|
|
options.wallet_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum.dat')
|
|
set_verbosity(options.verbose)
|
|
|
|
# config is an object passed to the various constructors (wallet, interface, gui)
|
|
if is_android:
|
|
config_options = {'wallet_path':"/sdcard/electrum.dat", 'portable':True, 'verbose':True, 'gui':'android', 'auto_cycle':True}
|
|
else:
|
|
config_options = eval(str(options))
|
|
for k, v in config_options.items():
|
|
if v is None: config_options.pop(k)
|
|
|
|
# Wallet migration on Electrum 1.7
|
|
# Todo: In time we could remove this again
|
|
if platform.system() == "Windows":
|
|
util.check_windows_wallet_migration()
|
|
|
|
config = SimpleConfig(config_options)
|
|
|
|
if len(args)==0:
|
|
url = None
|
|
cmd = 'gui'
|
|
elif len(args)==1 and re.match('^bitcoin:', args[0]):
|
|
url = args[0]
|
|
cmd = 'gui'
|
|
else:
|
|
cmd = args[0]
|
|
|
|
|
|
if cmd == 'gui':
|
|
gui_name = config.get('gui','classic')
|
|
if gui_name in ['lite', 'qt']: gui_name = 'classic'
|
|
try:
|
|
gui = __import__('electrum_gui.gui_' + gui_name, fromlist=['electrum_gui'])
|
|
except ImportError:
|
|
traceback.print_exc(file=sys.stdout)
|
|
sys.exit()
|
|
#sys.exit("Error: Unknown GUI: " + gui_name )
|
|
|
|
# network interface
|
|
network = Network(config)
|
|
network.start()
|
|
|
|
gui = gui.ElectrumGui(config, network)
|
|
gui.main(url)
|
|
|
|
network.stop()
|
|
# we use daemon threads, their termination is enforced.
|
|
# this sleep command gives them time to terminate cleanly.
|
|
time.sleep(0.1)
|
|
sys.exit(0)
|
|
|
|
|
|
# instanciate wallet for command-line
|
|
storage = WalletStorage(config)
|
|
wallet = Wallet(storage)
|
|
|
|
if cmd not in known_commands:
|
|
cmd = 'help'
|
|
|
|
if not storage.file_exists and cmd not in ['help','create','restore']:
|
|
print_msg("Error: Wallet file not found.")
|
|
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
|
|
sys.exit(0)
|
|
|
|
if cmd in ['create', 'restore']:
|
|
if wallet.storage.file_exists:
|
|
sys.exit("Error: Remove the existing wallet first!")
|
|
if options.password != None:
|
|
password = options.password
|
|
else:
|
|
password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
|
|
|
|
# if config.server is set, the user either passed the server on command line
|
|
# or chose it previously already. if he didn't pass a server on the command line,
|
|
# we just pick up a random one.
|
|
if not config.get('server'):
|
|
config.set_key('server', pick_random_server())
|
|
|
|
fee = options.tx_fee if options.tx_fee else raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
|
|
gap = options.gap_limit if options.gap_limit else raw_input("gap limit (default 5):")
|
|
|
|
if fee: wallet.set_fee(float(fee)*100000000)
|
|
if gap: wallet.change_gap_limit(int(gap))
|
|
|
|
if cmd == 'restore':
|
|
seed = raw_input("seed:")
|
|
try:
|
|
seed.decode('hex')
|
|
except:
|
|
print_error("Warning: Not hex, trying decode.")
|
|
seed = mnemonic_decode( seed.split(' ') )
|
|
if not seed:
|
|
sys.exit("Error: No seed")
|
|
|
|
wallet.init_seed( str(seed) )
|
|
wallet.save_seed()
|
|
wallet.create_accounts()
|
|
print_msg("Recovering wallet...")
|
|
network = Network(config)
|
|
network.start()
|
|
wallet.start_threads(network)
|
|
wallet.update()
|
|
if wallet.is_found():
|
|
print_msg("Recovery successful")
|
|
else:
|
|
print_msg("Warning: Found no history for this wallet")
|
|
|
|
else:
|
|
wallet.init_seed(None)
|
|
wallet.save_seed()
|
|
wallet.create_accounts()
|
|
wallet.synchronize()
|
|
print_msg("Your wallet generation seed is: " + wallet.seed)
|
|
print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
|
|
print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
|
|
print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
|
|
|
|
print_msg("Wallet saved in '%s'"%wallet.storage.path)
|
|
|
|
if password:
|
|
wallet.update_password(wallet.seed, None, password)
|
|
|
|
# terminate
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
# important warning
|
|
if cmd in ['dumpprivkey', 'dumpprivkeys']:
|
|
print_msg("WARNING: ALL your private keys are secret.")
|
|
print_msg("Exposing a single private key can compromise your entire wallet!")
|
|
print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
|
|
|
|
# commands needing password
|
|
if cmd in protected_commands:
|
|
if wallet.use_encryption:
|
|
password = prompt_password('Password:', False)
|
|
if not password:
|
|
print_msg("Error: Password required")
|
|
exit(1)
|
|
# check password
|
|
try:
|
|
seed = wallet.decode_seed(password)
|
|
except:
|
|
print_msg("Error: This password does not decode this wallet.")
|
|
exit(1)
|
|
else:
|
|
password = None
|
|
seed = wallet.seed
|
|
else:
|
|
password = None
|
|
|
|
|
|
# add missing arguments, do type conversions
|
|
if cmd == 'importprivkey':
|
|
# See if they specificed a key on the cmd line, if not prompt
|
|
if len(args) == 1:
|
|
args[1] = prompt_password('Enter PrivateKey (will not echo):', False)
|
|
|
|
elif cmd == 'signrawtransaction':
|
|
args = [ cmd, args[1], json.loads(args[2]) if len(args)>2 else [], json.loads(args[3]) if len(args)>3 else []]
|
|
|
|
elif cmd == 'createmultisig':
|
|
args = [ cmd, int(args[1]), json.loads(args[2])]
|
|
|
|
elif cmd == 'createrawtransaction':
|
|
args = [ cmd, json.loads(args[1]), json.loads(args[2])]
|
|
|
|
elif cmd == 'listaddresses':
|
|
args = [cmd, options.show_all, options.show_balance, options.show_labels]
|
|
|
|
elif cmd in ['payto', 'mktx']:
|
|
domain = [options.from_addr] if options.from_addr else None
|
|
args = [ 'mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain ]
|
|
|
|
elif cmd in ['paytomany', 'mksendmanytx']:
|
|
domain = [options.from_addr] if options.from_addr else None
|
|
outputs = []
|
|
for i in range(1, len(args), 2):
|
|
if len(args) < i+2:
|
|
print_msg("Error: Mismatched arguments.")
|
|
exit(1)
|
|
outputs.append((args[i], Decimal(args[i+1])))
|
|
args = [ 'mksendmanytx', outputs, Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain ]
|
|
|
|
elif cmd == 'help':
|
|
if len(args) < 2:
|
|
print_help(parser)
|
|
|
|
|
|
|
|
# check the number of arguments
|
|
min_args, max_args, description, syntax, options_syntax = known_commands[cmd]
|
|
if len(args) - 1 < min_args:
|
|
print_msg("Not enough arguments")
|
|
print_msg("Syntax:", syntax)
|
|
sys.exit(1)
|
|
|
|
if max_args >= 0 and len(args) - 1 > max_args:
|
|
print_msg("too many arguments", args)
|
|
print_msg("Syntax:", syntax)
|
|
sys.exit(1)
|
|
|
|
if max_args < 0:
|
|
if len(args) > min_args + 1:
|
|
message = ' '.join(args[min_args:])
|
|
print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
|
|
args = args[0:min_args] + [ message ]
|
|
|
|
|
|
# open session
|
|
if cmd not in offline_commands and not options.offline:
|
|
network = Network(config)
|
|
network.register_callback('connected', lambda: sys.stderr.write("Connected to " + network.interface.connection_msg + "\n"))
|
|
if not network.start(wait=True):
|
|
print_msg("Not connected, aborting.")
|
|
sys.exit(1)
|
|
|
|
wallet.start_threads(network)
|
|
wallet.update()
|
|
|
|
|
|
|
|
# run the command
|
|
|
|
if cmd == 'deseed':
|
|
if not wallet.seed:
|
|
print_msg("Error: This wallet has no seed")
|
|
else:
|
|
ns = wallet.config.path + '.seedless'
|
|
print_msg("Warning: you are going to create a seedless wallet'\nIt will be saved in '%s'"%ns)
|
|
if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
|
|
wallet.config.path = ns
|
|
wallet.seed = ''
|
|
wallet.storage.put('seed', '', True)
|
|
wallet.use_encryption = False
|
|
wallet.storage.put('use_encryption', wallet.use_encryption, True)
|
|
for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
|
|
wallet.config.set_key('imported_keys',wallet.imported_keys, True)
|
|
print_msg("Done.")
|
|
else:
|
|
print_msg("Action canceled.")
|
|
|
|
elif cmd == 'getconfig':
|
|
key = args[1]
|
|
print_msg(config.get(key))
|
|
|
|
elif cmd == 'setconfig':
|
|
key, value = args[1:3]
|
|
if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
|
|
config.set_key(key, value, True)
|
|
print_msg(True)
|
|
else:
|
|
print_msg(False)
|
|
|
|
elif cmd == 'password':
|
|
new_password = prompt_password('New password:')
|
|
wallet.update_password(seed, password, new_password)
|
|
|
|
else:
|
|
run_command(cmd, password, args)
|
|
|
|
|
|
if cmd not in offline_commands and not options.offline:
|
|
wallet.stop_threads()
|
|
network.stop()
|
|
time.sleep(0.1)
|
|
sys.exit(0)
|