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