slightly more robust connection procedure
[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, getpass
20
21 try:
22     import ecdsa  
23 except:
24     print "python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'"
25     sys.exit(1)
26
27 try:
28     import aes
29 except:
30     print "AES does not seem to be installed. Try 'sudo pip install slowaes'"
31     sys.exit(1)
32
33
34 import electrum
35 from optparse import OptionParser
36 from decimal import Decimal
37
38 from electrum import Wallet, WalletSynchronizer, format_satoshis
39
40 known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'restore', 'payto', 'sendtx', 'password', 'addresses', 'history', 'label', 'mktx','seed','import','signmessage','verifymessage','eval','deseed','reseed']
41 offline_commands = ['password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed']
42 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
43
44 if __name__ == '__main__':
45
46     usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
47     parser = OptionParser(usage=usage)
48     parser.add_option("-g", "--gui", dest="gui", default="qt", help="gui")
49     parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
50     parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
51     parser.add_option("-m", "--mnemonic", action="store_true", dest="show_mnemonic", default=False, help="[seed] print the seed as mnemonic")
52     parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
53     parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
54     parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
55     parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
56     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.")
57     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")
58     parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
59     options, args = parser.parse_args()
60
61     wallet = Wallet()
62     wallet.set_path(options.wallet_path)
63     wallet.read()
64     wallet.remote_url = options.remote_url
65
66     if len(args)==0:
67         url = None
68         cmd = 'gui'
69     elif len(args)==1 and re.match('^bitcoin:', args[0]):
70         url = args[0]
71         cmd = 'gui'
72     else:
73         cmd = args[0]
74         firstarg = args[1] if len(args) > 1 else ''
75         
76     if cmd == 'gui':
77         if options.gui=='gtk':
78             import electrum.gui as gui
79         elif options.gui=='qt':
80             import electrum.gui_qt as gui
81         else:
82             print "unknown gui", options.gui
83             exit(1)
84
85         gui = gui.ElectrumGui(wallet)
86         WalletSynchronizer(wallet,True).start()
87
88         try:
89             found = wallet.file_exists
90             if not found:
91                 found = gui.restore_or_create()
92         except SystemExit, e:
93             exit(e)
94         except BaseException, e:
95             import traceback
96             traceback.print_exc(file=sys.stdout)
97             #gui.show_message(e.message)
98             exit(1)
99
100         if not found: exit(1)
101         gui.main(url)
102         wallet.save()
103         sys.exit(0)
104
105     if cmd not in known_commands:
106         cmd = 'help'
107
108     if not wallet.file_exists and cmd not in ['help','create','restore']:
109         print "Wallet file not found."
110         print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
111         sys.exit(0)
112     
113     if cmd in ['create', 'restore']:
114         from electrum import mnemonic
115         if wallet.file_exists:
116             print "remove the existing wallet first!"
117             sys.exit(0)
118         password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):")
119         if password:
120             password2 = getpass.getpass("Confirm password:")
121             if password != password2:
122                 print "error"
123                 sys.exit(1)
124         else:
125             password = None
126
127         w_host, w_port, w_protocol = wallet.server.split(':')
128         host = raw_input("server (default:%s):"%w_host)
129         port = raw_input("port (default:%s):"%w_port)
130         protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
131         fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
132         gap = raw_input("gap limit (default 5):")
133         if host: w_host = host
134         if port: w_port = port
135         if protocol: w_protocol = protocol
136         wallet.server = w_host + ':' + w_port + ':' +w_protocol
137         if fee: wallet.fee = float(fee)
138         if gap: wallet.gap_limit = int(gap)
139
140         if cmd == 'restore':
141             seed = raw_input("seed:")
142             try:
143                 seed.decode('hex')
144             except:
145                 print "not hex, trying decode"
146                 seed = mnemonic.mn_decode( seed.split(' ') )
147             if not seed:
148                 print "no seed"
149                 sys.exit(1)
150
151             wallet.seed = str(seed)
152             wallet.init_mpk( wallet.seed )
153             if not options.offline:
154                 WalletSynchronizer(wallet).start()
155                 print "recovering wallet..."
156                 wallet.up_to_date_event.clear()
157                 wallet.up_to_date = False
158                 wallet.update()
159                 if wallet.is_found():
160                     print "recovery successful"
161                 else:
162                     print "found no history for this wallet"
163             wallet.fill_addressbook()
164             wallet.save()
165             print "Wallet saved in '%s'"%options.wallet_path
166         else:
167             wallet.new_seed(None)
168             wallet.init_mpk( wallet.seed )
169             wallet.synchronize() # there is no wallet thread 
170             wallet.save()
171             print "Your wallet generation seed is: " + wallet.seed
172             print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
173             print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
174             print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
175             print "Wallet saved in '%s'"%wallet.path
176
177     # check syntax
178     if cmd in ['payto', 'mktx']:
179         try:
180             to_address = args[1]
181             amount = int( 100000000 * Decimal(args[2]) )
182             change_addr = None
183             label = ' '.join(args[3:])
184             if options.tx_fee: 
185                 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
186         except:
187             firstarg = cmd
188             cmd = 'help'
189
190     # open session
191     if cmd not in offline_commands and not options.offline:
192         WalletSynchronizer(wallet).start()
193         wallet.update()
194         wallet.save()
195
196     # check if --from_addr not in wallet (for mktx/payto)
197     is_temporary = False
198     from_addr = None
199     if options.from_addr:
200         from_addr = options.from_addr
201         if from_addr not in wallet.all_addresses():
202             is_temporary = True
203                 
204     # commands needing password
205     if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
206         password = getpass.getpass('Password:') if wallet.use_encryption and not is_temporary else None
207         # check password
208         try:
209             wallet.pw_decode( wallet.seed, password)
210         except:
211             print "invalid password"
212             exit(1)
213
214     if cmd == 'import':
215         keypair = args[1]
216         if wallet.import_key(keypair,password):
217             print "keypair imported"
218         else:
219             print "error"
220         wallet.save()
221
222     if cmd=='help':
223         cmd2 = firstarg
224         if cmd2 not in known_commands:
225             print "type 'electrum help <command>' to see the help for a specific command"
226             print "type 'electrum --help' to see the list of options"
227             print "list of commands:", ', '.join(known_commands)
228         elif cmd2 == 'balance':
229             print "Display the balance of your wallet or of an address."
230             print "syntax: balance [<address>]"
231         elif cmd2 == 'contacts':
232             print "show your list of contacts"
233         elif cmd2 == 'payto':
234             print "payto <recipient> <amount> [label]"
235             print "create and broadcast a transaction."
236             print "<recipient> can be a bitcoin address or a label"
237             print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address"
238         elif cmd2== 'sendtx':
239             print "sendtx <tx>"
240             print "broadcast a transaction to the network. <tx> must be in hexadecimal"
241         elif cmd2 == 'password':
242             print "change your password"
243         elif cmd2 == 'addresses':
244             print "show your list of addresses."
245             print "options:\n -a: show all addresses, including change addresses\n-k: show private keys\n-b: show the balance of addresses"
246         elif cmd2 == 'history':
247             print "show the transaction history"
248         elif cmd2 == 'label':
249             print "assign a label to an item"
250         elif cmd2 == 'gtk':
251             print "start the GUI"
252         elif cmd2 == 'mktx':
253             print "create a signed transaction. password protected"
254             print "syntax: mktx <recipient> <amount> [label]"
255             print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address"
256         elif cmd2 == 'seed':
257             print "print the generation seed of your wallet."
258             print "options:\n-m, --mnemonic : print the seed as mnemonic"
259         elif cmd2 == 'deseed':
260             print "remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'"
261         elif cmd2 == 'reseed':
262             print "restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key."
263         elif cmd2 == 'eval':
264             print "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\""
265
266     elif cmd == 'seed':
267         from electrum import mnemonic
268         seed = wallet.pw_decode( wallet.seed, password)
269         if options.show_mnemonic:
270             print ' '.join(mnemonic.mn_encode(seed))
271         else:
272             print seed
273
274     elif cmd == 'deseed':
275         if not wallet.seed:
276             print "Error: This wallet has no seed"
277         elif wallet.use_encryption:
278             print "Error: This wallet is encrypted"
279         else:
280             ns = options.wallet_path+'.seed'
281             print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(options.wallet_path,ns)
282             if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
283                 f = open(ns,'w')
284                 f.write(wallet.seed)
285                 f.close()
286                 wallet.seed = ''
287                 wallet.save()
288                 print "Done."
289             else:
290                 print "Action canceled."
291
292     elif cmd == 'reseed':
293         if wallet.seed:
294             print "This wallet already has a seed"
295         else:
296             ns = options.wallet_path+'.seed'
297             try:
298                 f = open(ns,'r')
299                 seed = f.read()
300                 f.close()
301             except:
302                 print "seed file not found"
303                 sys.exit()
304
305             mpk = wallet.master_public_key
306             wallet.seed = seed
307             wallet.use_encryption = False
308             wallet.init_mpk(seed)
309             if mpk == wallet.master_public_key:
310                 wallet.save()
311                 print "done"
312             else:
313                 print "error: master public key does not match"
314
315     elif cmd == 'validateaddress':
316         addr = args[1]
317         print wallet.is_valid(addr)
318
319     elif cmd == 'balance':
320         try:
321             addrs = args[1:]
322         except:
323             pass
324         if addrs == []:
325             c, u = wallet.get_balance()
326             if u:
327                 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
328             else:
329                 print Decimal( c ) / 100000000
330         else:
331             for addr in addrs:
332                 c, u = wallet.get_addr_balance(addr)
333                 if u:
334                     print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
335                 else:
336                     print "%s %s" % (addr, str(Decimal(c)/100000000))
337
338     elif cmd in [ 'contacts']:
339         for addr in wallet.addressbook:
340             print addr, "   ", wallet.labels.get(addr)
341
342     elif cmd == 'eval':
343         print eval(args[1])
344         wallet.save()
345
346     elif cmd in [ 'addresses']:
347         for addr in wallet.all_addresses():
348             if options.show_all or not wallet.is_change(addr):
349                 label = wallet.labels.get(addr)
350                 _type = ''
351                 if wallet.is_change(addr): _type = "[change]"
352                 if addr in wallet.imported_keys.keys(): _type = "[imported]"
353                 if label is None: label = ''
354                 if options.show_balance:
355                     h = wallet.history.get(addr,[])
356                     ni = no = 0
357                     for item in h:
358                         if item['is_input']:  ni += 1
359                         else:              no += 1
360                     b = "%d %d %s"%(no, ni, str(Decimal(wallet.get_addr_balance(addr)[0])/100000000))
361                 else: b=''
362                 if options.show_keys:
363                     addr += ':' + str(wallet.get_private_key_base58(addr, password))
364                 print addr, b, _type, label
365
366     if cmd == 'history':
367         lines = wallet.get_tx_history()
368         b = 0 
369         for line in lines:
370             import datetime
371             v = line['value'] 
372             b += v
373             try:
374                 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
375             except:
376                 print line['timestamp']
377                 time_str = 'pending'
378             label = line.get('label')
379             if not label: label = line['tx_hash']
380             else: label = label + ' '*(64 - len(label) )
381
382             print time_str , "  " + label + "  " + format_satoshis(v)+ "  "+ format_satoshis(b)
383         print "# balance: ", format_satoshis(b)
384
385     elif cmd == 'label':
386         try:
387             tx = args[1]
388             label = ' '.join(args[2:])
389         except:
390             print "syntax:  label <tx_hash> <text>"
391             sys.exit(1)
392         wallet.labels[tx] = label
393         wallet.save()
394             
395     elif cmd in ['payto', 'mktx']:
396         if from_addr and is_temporary:
397             if from_addr.find(":") == -1:
398                 keypair = from_addr + ":" + getpass.getpass('Private key:')
399             else:
400                 keypair = from_addr
401                 from_addr = keypair.split(':')[0]
402             if not wallet.import_key(keypair,password):
403                 print "invalid key pair"
404                 exit(1)
405             wallet.history[from_addr] = interface.retrieve_history(from_addr)
406             wallet.update_tx_history()
407             change_addr = from_addr
408
409         if options.change_addr:
410             change_addr = options.change_addr
411
412         for k, v in wallet.labels.items():
413             if v == to_address:
414                 to_address = k
415                 print "alias", to_address
416                 break
417             if change_addr and v == change_addr:
418                 change_addr = k
419         try:
420             tx = wallet.mktx( to_address, amount, label, password,
421                 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
422         except:
423             import traceback
424             traceback.print_exc(file=sys.stdout)
425             tx = None
426
427         if tx and cmd=='payto': 
428             r, h = wallet.sendtx( tx )
429             print h
430         else:
431             print tx
432
433         if is_temporary:
434             wallet.imported_keys.pop(from_addr)
435             del(wallet.history[from_addr])
436         wallet.save()
437
438     elif cmd == 'sendtx':
439         tx = args[1]
440         r, h = wallet.sendtx( tx )
441         print h
442
443     elif cmd == 'password':
444         try:
445             seed = wallet.pw_decode( wallet.seed, password)
446         except:
447             print "sorry"
448             sys.exit(1)
449         new_password = getpass.getpass('New password:')
450         if new_password == getpass.getpass('Confirm new password:'):
451             wallet.use_encryption = (new_password != '')
452             wallet.seed = wallet.pw_encode( seed, new_password)
453             for k in wallet.imported_keys.keys():
454                 a = wallet.imported_keys[k]
455                 b = wallet.pw_decode(a, password)
456                 c = wallet.pw_encode(b, new_password)
457                 wallet.imported_keys[k] = c
458             wallet.save()
459         else:
460             print "error: mismatch"
461
462     elif cmd == 'signmessage':
463         address, message = args[1:3]
464         print wallet.sign_message(address, message, password)
465
466     elif cmd == 'verifymessage':
467         address, signature, message = args[1:4]
468         try:
469             wallet.verify_message(address, signature, message)
470             print True
471         except:
472             print False
473         
474