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