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