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