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