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