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