Unneeded exception raised. sys.exit returns before exception ever gets a chance to...
[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 import re, sys
20 try:
21     from lib.util import print_error
22 except ImportError:
23     from electrum.util import print_error
24
25 try:
26     import ecdsa  
27 except ImportError:
28     sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
29
30 try:
31     import aes
32 except ImportError:
33     sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
34
35 try:
36     from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
37 except ImportError:
38     from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
39     
40 from optparse import OptionParser
41 from decimal import Decimal
42
43 known_commands = {
44     'help':'Prints this help',
45     'validateaddress':'Check that the address is valid', 
46     'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]", 
47     'contacts': "Show your list of contacts", 
48     'create':'Create a wallet', 
49     'restore':'Restore a wallet', 
50     'payto':"""Create and broadcast a transaction.
51 Syntax: payto <recipient> <amount> [label]
52 <recipient> can be a bitcoin address or a label
53 options:\n  --fee, -f: set transaction fee\n  --fromaddr, -s: send from address -\n  --changeaddr, -c: send change to address
54             """,
55     'sendtx':
56             'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
57     'password': 
58             "Changes your password",
59     'addresses':  
60             """Shows your list of addresses.
61 options:
62   -a: show all addresses, including change addresses
63   -k: show private keys
64   -b: show the balance of addresses""",
65
66     'history':"Shows the transaction history",
67     'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
68     'mktx':
69         """Create a signed transaction, password protected.
70 Syntax: mktx <recipient> <amount> [label]
71 options:\n  --fee, -f: set transaction fee\n  --fromaddr, -s: send from address -\n  --changeaddr, -c: send change to address
72         """,
73     'seed':
74             "Print the generation seed of your wallet.",
75     'import': 
76             'Imports a key pair\nSyntax: import <address>:<privatekey>',
77     'signmessage':
78             'Signs a message with a key\nSyntax: signmessage <address> <message>',
79     'verifymessage':
80              'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>',
81     'eval':  
82              "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
83     'deseed':
84             "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
85     'reseed':
86             "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
87     'freeze':'',
88     'unfreeze':'',
89     'prioritize':'',
90     'unprioritize':'',
91     }
92
93
94
95 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
96
97 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
98
99 if __name__ == '__main__':
100
101     usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
102     parser = OptionParser(usage=usage)
103     parser.add_option("-g", "--gui", dest="gui", default="lite", help="gui")
104     parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
105     parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
106     parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
107     parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
108     parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
109     parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
110     parser.add_option("-s", "--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.")
111     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")
112     parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
113     options, args = parser.parse_args()
114
115     wallet = Wallet()
116     wallet.set_path(options.wallet_path)
117     wallet.read()
118     wallet.remote_url = options.remote_url
119
120     if len(args)==0:
121         url = None
122         cmd = 'gui'
123     elif len(args)==1 and re.match('^bitcoin:', args[0]):
124         url = args[0]
125         cmd = 'gui'
126     else:
127         cmd = args[0]
128         firstarg = args[1] if len(args) > 1 else ''
129        
130     #this entire if/else block is just concerned with importing the 
131     #right GUI toolkit based the GUI command line option given 
132     if cmd == 'gui':
133         
134         if options.gui=='gtk':
135             try:
136                 import lib.gui as gui
137             except ImportError:
138                 import electrum.gui as gui
139         elif options.gui=='qt':
140             try:
141                 import lib.gui_qt as gui
142             except ImportError:
143                 import electrum.gui_qt as gui
144         elif options.gui == 'lite':
145             # Let's do some dep checking and handle missing ones gracefully
146             try:
147               from PyQt4.QtCore import *
148               from PyQt4.QtGui import *
149             except ImportError:
150               print "You need to have PyQT installed to run Electrum in graphical mode."
151               print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
152               sys.exit(0)
153
154             qtVersion = qVersion()
155             if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
156               app = QApplication(sys.argv)
157
158               error_message = QErrorMessage()
159               error_message.setFixedSize(350,200)
160               error_message.showMessage("<p>Sorry, Electrum requires Qt >= 4.7 to run.</p><p>Check your distributions packages or download it at http://qt.nokia.com/downloads</p>")
161               app.exec_()
162               sys.exit(0)
163            
164             #use the lite version if no toolkit specified
165             try:
166                 import lib.gui_lite as gui
167             except ImportError:
168                 import electrum.gui_lite as gui
169         else:
170             sys.exit("Error: Unknown GUI: " + options.gui)
171
172         gui = gui.ElectrumGui(wallet)
173         WalletSynchronizer(wallet,True).start()
174
175         try:
176             found = wallet.file_exists
177             if not found:
178                 found = gui.restore_or_create()
179         except SystemExit, e:
180             exit(e)
181         except BaseException, e:
182             import traceback
183             traceback.print_exc(file=sys.stdout)
184             #gui.show_message(e.message)
185             exit(1)
186
187         if not found:
188             exit(1)
189         gui.main(url)
190         wallet.save()
191         sys.exit(0)
192
193     if cmd not in known_commands:
194         cmd = 'help'
195
196     if not wallet.file_exists and cmd not in ['help','create','restore']:
197         print "Error: Wallet file not found."
198         print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
199         sys.exit(0)
200     
201     if cmd in ['create', 'restore']:
202         if wallet.file_exists:
203             sys.exit("Error: Remove the existing wallet first!")
204             password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
205
206         w_host, w_port, w_protocol = wallet.server.split(':')
207         host = raw_input("server (default:%s):"%w_host)
208         port = raw_input("port (default:%s):"%w_port)
209         protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
210         fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
211         gap = raw_input("gap limit (default 5):")
212         if host: w_host = host
213         if port: w_port = port
214         if protocol: w_protocol = protocol
215         wallet.server = w_host + ':' + w_port + ':' +w_protocol
216         if fee: wallet.fee = float(fee)
217         if gap: wallet.gap_limit = int(gap)
218
219         if cmd == 'restore':
220             seed = raw_input("seed:")
221             try:
222                 seed.decode('hex')
223             except:
224                 print_error("Warning: Not hex, trying decode.")
225                 seed = mnemonic.mn_decode( seed.split(' ') )
226             if not seed:
227                 sys.exit("Error: No seed")
228
229             wallet.seed = str(seed)
230             wallet.init_mpk( wallet.seed )
231             if not options.offline:
232                 WalletSynchronizer(wallet).start()
233                 print "Recovering wallet..."
234                 wallet.up_to_date_event.clear()
235                 wallet.up_to_date = False
236                 wallet.update()
237                 if wallet.is_found():
238                     print "Recovery successful"
239                 else:
240                     print_error("Warning: Found no history for this wallet")
241             wallet.fill_addressbook()
242             wallet.save()
243             print_error("Wallet saved in '" + wallet.path)
244         else:
245             wallet.new_seed(None)
246             wallet.init_mpk( wallet.seed )
247             wallet.synchronize() # there is no wallet thread 
248             wallet.save()
249             print "Your wallet generation seed is: " + wallet.seed
250             print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
251             print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
252             print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
253             print "Wallet saved in '%s'"%wallet.path
254             
255         if password:
256             wallet.update_password(wallet.seed, None, password)
257
258     # check syntax
259     if cmd in ['payto', 'mktx']:
260         try:
261             to_address = args[1]
262             amount = int( 100000000 * Decimal(args[2]) )
263             change_addr = None
264             label = ' '.join(args[3:])
265             if options.tx_fee: 
266                 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
267         except:
268             firstarg = cmd
269             cmd = 'help'
270
271     # open session
272     if cmd not in offline_commands and not options.offline:
273         WalletSynchronizer(wallet).start()
274         wallet.update()
275         wallet.save()
276
277     # check if --from_addr not in wallet (for mktx/payto)
278     is_temporary = False
279     from_addr = None
280     if options.from_addr:
281         from_addr = options.from_addr
282         if from_addr not in wallet.all_addresses():
283             is_temporary = True
284                 
285     # commands needing password
286     if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
287         password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
288         # check password
289         try:
290             wallet.pw_decode( wallet.seed, password)
291         except:
292             print_error("Error: This password does not decode this wallet.")
293             exit(1)
294
295     if cmd == 'import':
296         # See if they specificed a key on the cmd line, if not prompt
297         if len(args) > 1:
298             keypair = args[1]
299         else:
300             keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
301         try:
302             wallet.import_key(keypair,password)
303             wallet.save()
304             print "Keypair imported"
305         except BaseException, e:
306             print_error("Error: Keypair import failed: " + str(e))
307
308     if cmd == 'help':
309         cmd2 = firstarg
310         if cmd2 not in known_commands:
311             parser.print_help()
312             print
313             print "Type 'electrum help <command>' to see the help for a specific command"
314             print "Type 'electrum --help' to see the list of options"
315             print "List of commands:", ', '.join(known_commands)
316         else:
317             print known_commands[cmd2]
318
319     elif cmd == 'seed':
320         seed = wallet.pw_decode( wallet.seed, password)
321         print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
322
323     elif cmd == 'deseed':
324         if not wallet.seed:
325             print_error("Error: This wallet has no seed")
326         elif wallet.use_encryption:
327             print_error("Error: This wallet is encrypted")
328         else:
329             ns = wallet.path + '.seed'
330             print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
331             if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
332                 f = open(ns,'w')
333                 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
334                 f.close()
335                 wallet.seed = ''
336                 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
337                 wallet.save()
338                 print "Done."
339             else:
340                 print_error("Action canceled.")
341
342     elif cmd == 'reseed':
343         if wallet.seed:
344             print "Warning: This wallet already has a seed", wallet.seed
345         else:
346             ns = wallet.path + '.seed'
347             try:
348                 f = open(ns,'r')
349                 data = f.read()
350                 f.close()
351             except IOError:
352                 sys.exit("Error: Seed file not found")
353             try:
354                 import ast
355                 d = ast.literal_eval( data )
356                 seed = d['seed']
357                 imported_keys = d.get('imported_keys',{})
358             except IOError:
359                 sys.exit("Error: Error with seed file")
360
361             mpk = wallet.master_public_key
362             wallet.seed = seed
363             wallet.imported_keys = imported_keys
364             wallet.use_encryption = False
365             wallet.init_mpk(seed)
366             if mpk == wallet.master_public_key:
367                 wallet.save()
368                 print "Done: " + wallet.path
369             else:
370                 print_error("Error: Master public key does not match")
371
372     elif cmd == 'validateaddress':
373         addr = args[1]
374         print wallet.is_valid(addr)
375
376     elif cmd == 'balance':
377         try:
378             addrs = args[1:]
379         except:
380             pass
381         if addrs == []:
382             c, u = wallet.get_balance()
383             if u:
384                 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
385             else:
386                 print Decimal( c ) / 100000000
387         else:
388             for addr in addrs:
389                 c, u = wallet.get_addr_balance(addr)
390                 if u:
391                     print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
392                 else:
393                     print "%s %s" % (addr, str(Decimal(c)/100000000))
394
395     elif cmd in [ 'contacts']:
396         for addr in wallet.addressbook:
397             print addr, "   ", wallet.labels.get(addr)
398
399     elif cmd == 'eval':
400         print eval(args[1])
401         wallet.save()
402
403     elif cmd in [ 'addresses']:
404         for addr in wallet.all_addresses():
405             if options.show_all or not wallet.is_change(addr):
406
407                 flags = wallet.get_address_flags(addr)
408                 label = wallet.labels.get(addr,'')
409                 
410                 if label: label = "\"%s\""%label
411
412                 if options.show_balance:
413                     h = wallet.history.get(addr,[])
414                     #ni = no = 0
415                     #for item in h:
416                     #    if item['is_input']:  ni += 1
417                     #    else:              no += 1
418                     b = format_satoshis(wallet.get_addr_balance(addr)[0])
419                 else: b=''
420                 m_addr = "%34s"%addr
421                 if options.show_keys:
422                     m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
423                 print flags, m_addr, b, label
424
425     if cmd == 'history':
426         lines = wallet.get_tx_history()
427         b = 0 
428         for line in lines:
429             import datetime
430             v = line['value'] 
431             b += v
432             try:
433                 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
434             except:
435                 print line['timestamp']
436                 time_str = 'pending'
437             label = line.get('label')
438             if not label: label = line['tx_hash']
439             else: label = label + ' '*(64 - len(label) )
440
441             print time_str , "  " + label + "  " + format_satoshis(v)+ "  "+ format_satoshis(b)
442         print "# balance: ", format_satoshis(b)
443
444     elif cmd == 'label':
445         try:
446             tx = args[1]
447             label = ' '.join(args[2:])
448         except:
449             print_error("Error. Syntax:  label <tx_hash> <text>")
450             sys.exit(1)
451         wallet.labels[tx] = label
452         wallet.save()
453             
454     elif cmd in ['payto', 'mktx']:
455         if from_addr and is_temporary:
456             if from_addr.find(":") == -1:
457                 keypair = from_addr + ":" + prompt_password('Private key:', False)
458             else:
459                 keypair = from_addr
460                 from_addr = keypair.split(':')[0]
461             if not wallet.import_key(keypair,password):
462                 print_error("Error: Invalid key pair")
463                 exit(1)
464             wallet.history[from_addr] = interface.retrieve_history(from_addr)
465             wallet.update_tx_history()
466             change_addr = from_addr
467
468         if options.change_addr:
469             change_addr = options.change_addr
470
471         for k, v in wallet.labels.items():
472             if v == to_address:
473                 to_address = k
474                 print "alias", to_address
475                 break
476             if change_addr and v == change_addr:
477                 change_addr = k
478         try:
479             tx = wallet.mktx( to_address, amount, label, password,
480                 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
481         except:
482             import traceback
483             traceback.print_exc(file=sys.stdout)
484             tx = None
485
486         if tx and cmd=='payto': 
487             r, h = wallet.sendtx( tx )
488             print h
489         else:
490             print tx
491
492         if is_temporary:
493             wallet.imported_keys.pop(from_addr)
494             del(wallet.history[from_addr])
495         wallet.save()
496
497     elif cmd == 'sendtx':
498         tx = args[1]
499         r, h = wallet.sendtx( tx )
500         print h
501
502     elif cmd == 'password':
503         try:
504             seed = wallet.pw_decode( wallet.seed, password)
505         except StandardError:
506             sys.exit("Error: Password does not decrypt this wallet.")
507
508         new_password = prompt_password('New password:')
509         wallet.update_password(seed, password, new_password)
510
511     elif cmd == 'signmessage':
512         if len(args) < 3:
513             print_error("Error: Invalid usage of signmessage.")
514             print known_commands[cmd]
515             sys.exit(1)
516         address = args[1]
517         message = ' '.join(args[2:])
518         if len(args) > 3:
519             print "Warning: Message was reconstructed from several arguments:", repr(message)
520         print wallet.sign_message(address, message, password)
521
522     elif cmd == 'verifymessage':
523         try:
524             address = args[1]
525             signature = args[2]
526             message = ' '.join(args[3:])
527         except:
528             print_error("Error: Not all parameters were given, displaying help instead.")
529             print known_commands[cmd]
530             sys.exit(1)
531         if len(args) > 4:
532             print "Warning: Message was reconstructed from several arguments:", repr(message)
533         try:
534             wallet.verify_message(address, signature, message)
535             print True
536         except:
537             print False
538
539     elif cmd == 'freeze':
540         addr = args[1]
541         print self.wallet.freeze(addr)
542         
543     elif cmd == 'unfreeze':
544         addr = args[1]
545         print self.wallet.unfreeze(addr)
546
547     elif cmd == 'prioritize':
548         addr = args[1]
549         print self.wallet.prioritize(addr)
550
551     elif cmd == 'unprioritize':
552         addr = args[1]
553         print self.wallet.unprioritize(addr)
554