wizard restore hook
[electrum-nvc.git] / electrum
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 from decimal import Decimal
20 import json
21 import optparse
22 import os
23 import re
24 import ast
25 import sys
26 import time
27 import traceback
28
29
30 is_local = os.path.dirname(os.path.realpath(__file__)) == os.getcwd()
31 is_android = 'ANDROID_DATA' in os.environ
32
33 import __builtin__
34 __builtin__.use_local_modules = is_local or is_android
35
36 # load local module as electrum
37 if __builtin__.use_local_modules:
38     import imp
39     imp.load_module('electrum', *imp.find_module('lib'))
40     imp.load_module('electrum_gui', *imp.find_module('gui'))
41
42 if is_local:
43     sys.path.append('packages')
44
45
46 from electrum import SimpleConfig, Network, Wallet, WalletStorage, NetworkProxy, Commands, known_commands, pick_random_server
47 from electrum.util import print_msg, print_stderr, print_json, set_verbosity
48
49 # get password routine
50 def prompt_password(prompt, confirm=True):
51     import getpass
52     if sys.stdin.isatty():
53         password = getpass.getpass(prompt)
54         if password and confirm:
55             password2 = getpass.getpass("Confirm: ")
56             if password != password2:
57                 sys.exit("Error: Passwords do not match.")
58     else:
59         password = raw_input(prompt)
60     if not password:
61         password = None
62     return password
63
64
65 def arg_parser():
66     usage = "%prog [options] command"
67     parser = optparse.OptionParser(usage=usage, add_help_option=False)
68     parser.add_option("-h", "--help", action="callback", callback=print_help_cb, help="show this help text")
69     parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk, text or stdio")
70     parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
71     parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
72     parser.add_option("-C", "--concealed", action="store_true", dest="concealed", default=False, help="don't echo seed to console when restoring")
73     parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
74     parser.add_option("-l", "--labels", action="store_true", dest="show_labels", default=False, help="show the labels of listed addresses")
75     parser.add_option("-f", "--fee", dest="tx_fee", default=None, help="set tx fee")
76     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.")
77     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")
78     parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp), h (http), s (tcp+ssl), or g (https)")
79     parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
80     parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
81     parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
82     parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
83     parser.add_option("-u", "--usb", dest="bitkey", action="store_true", help="Turn on support for hardware wallets (EXPERIMENTAL)")
84     parser.add_option("-G", "--gap", dest="gap_limit", default=None, help="gap limit")
85     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)")
86     parser.add_option("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only")
87     parser.add_option("--bip32", action="store_true", dest="bip32", default=False, help="bip32 (not final)")
88     parser.add_option("--2of3", action="store_true", dest="2of3", default=False, help="create 2of3 wallet")
89     parser.add_option("--mpk", dest="mpk", default=False, help="restore from master public key")
90     parser.add_option("-m", action="store_true", dest="hide_gui", default=False, help="hide GUI on startup")
91     return parser
92
93
94 def print_help(parser):
95     parser.print_help()
96     print_msg("Type 'electrum help <command>' to see the help for a specific command")
97     print_msg("Type 'electrum --help' to see the list of options")
98     run_command(known_commands['help'])
99
100
101 def print_help_cb(self, opt, value, parser):
102     print_help(parser)
103     sys.exit(1)
104
105
106
107
108 def run_command(cmd, password=None, args=None):
109     if args is None:
110         args = []  # Do not use mutables as default values!
111     if cmd.requires_network and not options.offline:
112         network = NetworkProxy(config)
113         if not network.start(start_daemon= (True if cmd.name!='daemon' else False)):
114             print "Daemon not running"
115             sys.exit(1)
116
117         if wallet:
118             wallet.start_threads(network)
119             wallet.update()
120     else:
121         network = None
122
123     cmd_runner = Commands(wallet, network)
124     func = getattr(cmd_runner, cmd.name)
125     cmd_runner.password = password
126     try:
127         result = func(*args[1:])
128     except Exception:
129         traceback.print_exc(file=sys.stdout)
130         sys.exit(1)
131
132
133     if cmd.requires_network and not options.offline:
134         if wallet:
135             wallet.stop_threads()
136
137
138     if type(result) == str:
139         print_msg(result)
140     elif result is not None:
141         print_json(result)
142
143
144
145
146
147
148 if __name__ == '__main__':
149
150     wallet = None
151     parser = arg_parser()
152     options, args = parser.parse_args()
153     if options.portable and options.wallet_path is None:
154         options.electrum_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data')
155
156     # config is an object passed to the various constructors (wallet, interface, gui)
157     if is_android:
158         config_options = {
159             'portable': True,
160             'verbose': True,
161             'gui': 'android',
162             'auto_cycle': True,
163         }
164     else:
165         config_options = eval(str(options))
166         for k, v in config_options.items():
167             if v is None:
168                 config_options.pop(k)
169
170     set_verbosity(config_options.get('verbose'))
171
172     config = SimpleConfig(config_options)
173
174     if len(args) == 0:
175         url = None
176         cmd = 'gui'
177     elif len(args) == 1 and re.match('^bitcoin:', args[0]):
178         url = args[0]
179         cmd = 'gui'
180     else:
181         cmd = args[0]
182
183     if cmd == 'gui':
184         gui_name = config.get('gui', 'classic')
185         if gui_name in ['lite', 'classic']:
186             gui_name = 'qt'
187         try:
188             gui = __import__('electrum_gui.' + gui_name, fromlist=['electrum_gui'])
189         except ImportError:
190             traceback.print_exc(file=sys.stdout)
191             sys.exit()
192             #sys.exit("Error: Unknown GUI: " + gui_name )
193
194         # network interface
195         if not options.offline:
196             network = Network(config)
197             network.start()
198         else:
199             network = None
200
201         gui = gui.ElectrumGui(config, network)
202         gui.main(url)
203
204         if network:
205             network.stop()
206
207         # we use daemon threads, their termination is enforced.
208         # this sleep command gives them time to terminate cleanly.
209         time.sleep(0.1)
210         sys.exit(0)
211
212     if cmd not in known_commands:
213         cmd = 'help'
214
215     cmd = known_commands[cmd]
216
217     # instanciate wallet for command-line
218     storage = WalletStorage(config)
219
220
221     if cmd.name in ['create', 'restore']:
222         if storage.file_exists:
223             sys.exit("Error: Remove the existing wallet first!")
224         if options.password is not None:
225             password = options.password
226         elif cmd.name == 'restore' and options.mpk:
227             password = None
228         else:
229             password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
230
231         # if config.server is set, the user either passed the server on command line
232         # or chose it previously already. if he didn't pass a server on the command line,
233         # we just pick up a random one.
234         if not config.get('server'):
235             config.set_key('server', pick_random_server())
236
237         #fee = options.tx_fee if options.tx_fee else raw_input("fee (default:%s):" % (str(Decimal(wallet.fee)/100000000)))
238         #gap = options.gap_limit if options.gap_limit else raw_input("gap limit (default 5):")
239         #if fee:
240         #    wallet.set_fee(float(fee)*100000000)
241         #if gap:
242         #    wallet.change_gap_limit(int(gap))
243
244         if cmd.name == 'restore':
245             if options.mpk:
246                 wallet = Wallet.from_mpk(options.mpk, storage)
247             else:
248                 import getpass
249                 seed = getpass.getpass(prompt="seed:", stream=None) if options.concealed else raw_input("seed:")
250                 if not Wallet.is_seed(seed):
251                     sys.exit("Error: Invalid seed")
252                 wallet = Wallet.from_seed(seed, storage)
253                 wallet.add_seed(seed, password)
254                 wallet.create_accounts(password)
255
256             if not options.offline:
257                 network = Network(config)
258                 network.start()
259                 wallet.start_threads(network)
260                 print_msg("Recovering wallet...")
261                 wallet.restore(lambda x: x)
262                 if wallet.is_found():
263                     print_msg("Recovery successful")
264                 else:
265                     print_msg("Warning: Found no history for this wallet")
266             else:
267                 wallet.synchronize()
268                 print_msg("Warning: This wallet was restored offline. It may contain more addresses than displayed.")
269
270         else:
271             if not config.get('2of3'):
272                 wallet = Wallet(storage)
273                 seed = wallet.make_seed()
274                 wallet.add_seed(seed, password)
275                 wallet.create_accounts(password)
276                 wallet.synchronize()
277                 print_msg("Your wallet generation seed is:\n\"%s\"" % seed)
278                 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
279             else:
280                 wallet = Wallet_2of3(storage)
281                 cold_seed = wallet.make_seed()
282                 #wallet.save_seed()
283                 print_msg("Your cold seed is:\n\"%s\"" % cold_seed)
284                 print_msg("Please store it on paper. ")
285                 print_msg("Open this file on your online computer to complete your wallet creation.")
286
287
288         print_msg("Wallet saved in '%s'" % wallet.storage.path)
289
290         # terminate
291         sys.exit(0)
292
293
294     if cmd.name not in ['create', 'restore'] and cmd.requires_wallet and not storage.file_exists:
295         print_msg("Error: Wallet file not found.")
296         print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
297         sys.exit(0)
298
299
300     if cmd.requires_wallet:
301         wallet = Wallet(storage)
302     else:
303         wallet = None
304
305
306     # important warning
307     if cmd.name in ['dumpprivkey', 'dumpprivkeys']:
308         print_stderr("WARNING: ALL your private keys are secret.")
309         print_stderr("Exposing a single private key can compromise your entire wallet!")
310         print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
311
312     # commands needing password
313     if cmd.requires_password:
314         if wallet.seed == '':
315             seed = ''
316             password = None
317         elif wallet.use_encryption:
318             password = prompt_password('Password:', False)
319             if not password:
320                 print_msg("Error: Password required")
321                 sys.exit(1)
322             # check password
323             try:
324                 seed = wallet.get_seed(password)
325             except Exception:
326                 print_msg("Error: This password does not decode this wallet.")
327                 sys.exit(1)
328         else:
329             password = None
330             seed = wallet.get_seed(None)
331     else:
332         password = None
333
334     # add missing arguments, do type conversions
335     if cmd.name == 'importprivkey':
336         # See if they specificed a key on the cmd line, if not prompt
337         if len(args) == 1:
338             args.append(prompt_password('Enter PrivateKey (will not echo):', False))
339
340     elif cmd.name == 'signrawtransaction':
341         args = [cmd, args[1], json.loads(args[2]) if len(args) > 2 else [] ]
342
343     elif cmd.name == 'createmultisig':
344         args = [cmd, int(args[1]), json.loads(args[2])]
345
346     elif cmd.name == 'createrawtransaction':
347         args = [cmd, json.loads(args[1]), json.loads(args[2])]
348
349     elif cmd.name == 'listaddresses':
350         args = [cmd, options.show_all, options.show_labels]
351
352     elif cmd.name in ['payto', 'mktx']:
353         domain = [options.from_addr] if options.from_addr else None
354         args = ['mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
355
356     elif cmd.name in ['paytomany', 'mksendmanytx']:
357         domain = [options.from_addr] if options.from_addr else None
358         outputs = []
359         for i in range(1, len(args), 2):
360             if len(args) < i+2:
361                 print_msg("Error: Mismatched arguments.")
362                 sys.exit(1)
363             outputs.append((args[i], Decimal(args[i+1])))
364         args = ['mksendmanytx', outputs, Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
365
366     elif cmd.name == 'help':
367         if len(args) < 2:
368             print_help(parser)
369             sys.exit(1)
370
371     # check the number of arguments
372     if len(args) - 1 < cmd.min_args:
373         print_msg("Not enough arguments")
374         print_msg("Syntax:", cmd.syntax)
375         sys.exit(1)
376
377     if cmd.max_args >= 0 and len(args) - 1 > cmd.max_args:
378         print_msg("too many arguments", args)
379         print_msg("Syntax:", cmd.syntax)
380         sys.exit(1)
381
382     if cmd.max_args < 0:
383         if len(args) > cmd.min_args + 1:
384             message = ' '.join(args[cmd.min_args:])
385             print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
386             args = args[0:cmd.min_args] + [message]
387
388
389
390     # run the command
391     if cmd.name == 'deseed':
392         if not wallet.seed:
393             print_msg("Error: This wallet has no seed")
394         else:
395             ns = wallet.storage.path + '.seedless'
396             print_msg("Warning: you are going to create a seedless wallet'\nIt will be saved in '%s'" % ns)
397             if raw_input("Are you sure you want to continue? (y/n) ") in ['y', 'Y', 'yes']:
398                 wallet.storage.path = ns
399                 wallet.seed = ''
400                 wallet.storage.put('seed', '', True)
401                 wallet.use_encryption = False
402                 wallet.storage.put('use_encryption', wallet.use_encryption, True)
403                 for k in wallet.imported_keys.keys():
404                     wallet.imported_keys[k] = ''
405                 wallet.storage.put('imported_keys', wallet.imported_keys, True)
406                 print_msg("Done.")
407             else:
408                 print_msg("Action canceled.")
409
410     elif cmd.name == 'getconfig':
411         key = args[1]
412         out = config.get(key)
413         print_msg(out)
414
415     elif cmd.name == 'setconfig':
416         key, value = args[1:3]
417         try:
418             value = ast.literal_eval(value)
419         except:
420             pass
421         config.set_key(key, value, True)
422         print_msg(True)
423
424     elif cmd.name == 'password':
425         new_password = prompt_password('New password:')
426         wallet.update_password(password, new_password)
427
428     else:
429         run_command(cmd, password, args)
430
431
432     time.sleep(0.1)
433     sys.exit(0)