3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
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.
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.
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/>.
19 import re, sys, getpass
24 print "python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'"
30 print "AES does not seem to be installed. Try 'sudo pip install slowaes'"
34 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic
36 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic
38 from optparse import OptionParser
39 from decimal import Decimal
43 'validateaddress':'check that the address is valid',
44 'balance': "Display the balance of your wallet or of an address.\nsyntax: balance [<address>]",
45 'contacts': "Show your list of contacts",
46 'create':'create wallet',
47 'restore':'restore wallet',
49 payto <recipient> <amount> [label]
50 create and broadcast a transaction.
51 <recipient> can be a bitcoin address or a label
52 options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address
56 broadcast a transaction to the network. <tx> must be in hexadecimal""",
58 "change your password",
60 """show your list of addresses.
62 -a: show all addresses, including change addresses
64 -b: show the balance of addresses""",
65 'history':"show the transaction history",
66 'label':"assign a label to an item",
68 """create a signed transaction. password protected
69 syntax: mktx <recipient> <amount> [label]
70 options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address
73 "print the generation seed of your wallet.",
77 'sign a message with a key',
81 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
83 "remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
85 "restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
94 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
96 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
98 if __name__ == '__main__':
100 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
101 parser = OptionParser(usage=usage)
102 parser.add_option("-g", "--gui", dest="gui", default="qt", help="gui")
103 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
104 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
105 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
106 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
107 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
108 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
109 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.")
110 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")
111 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
112 options, args = parser.parse_args()
115 wallet.set_path(options.wallet_path)
117 wallet.remote_url = options.remote_url
122 elif len(args)==1 and re.match('^bitcoin:', args[0]):
127 firstarg = args[1] if len(args) > 1 else ''
130 if options.gui=='gtk':
132 import lib.gui as gui
134 import electrum.gui as gui
135 elif options.gui=='qt':
137 import lib.gui_qt as gui
139 import electrum.gui_qt as gui
141 print "unknown gui", options.gui
144 gui = gui.ElectrumGui(wallet)
145 WalletSynchronizer(wallet,True).start()
148 found = wallet.file_exists
150 found = gui.restore_or_create()
151 except SystemExit, e:
153 except BaseException, e:
155 traceback.print_exc(file=sys.stdout)
156 #gui.show_message(e.message)
159 if not found: exit(1)
164 if cmd not in known_commands:
167 if not wallet.file_exists and cmd not in ['help','create','restore']:
168 print "Wallet file not found."
169 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
172 if cmd in ['create', 'restore']:
173 if wallet.file_exists:
174 print "remove the existing wallet first!"
176 password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):")
178 password2 = getpass.getpass("Confirm password:")
179 if password != password2:
185 w_host, w_port, w_protocol = wallet.server.split(':')
186 host = raw_input("server (default:%s):"%w_host)
187 port = raw_input("port (default:%s):"%w_port)
188 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
189 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
190 gap = raw_input("gap limit (default 5):")
191 if host: w_host = host
192 if port: w_port = port
193 if protocol: w_protocol = protocol
194 wallet.server = w_host + ':' + w_port + ':' +w_protocol
195 if fee: wallet.fee = float(fee)
196 if gap: wallet.gap_limit = int(gap)
199 seed = raw_input("seed:")
203 print "not hex, trying decode"
204 seed = mnemonic.mn_decode( seed.split(' ') )
209 wallet.seed = str(seed)
210 wallet.init_mpk( wallet.seed )
211 if not options.offline:
212 WalletSynchronizer(wallet).start()
213 print "recovering wallet..."
214 wallet.up_to_date_event.clear()
215 wallet.up_to_date = False
217 if wallet.is_found():
218 print "recovery successful"
220 print "found no history for this wallet"
221 wallet.fill_addressbook()
223 print "Wallet saved in '%s'"%wallet.path
225 wallet.new_seed(None)
226 wallet.init_mpk( wallet.seed )
227 wallet.synchronize() # there is no wallet thread
229 print "Your wallet generation seed is: " + wallet.seed
230 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
231 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
232 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
233 print "Wallet saved in '%s'"%wallet.path
236 wallet.update_password(wallet.seed, None, password)
239 if cmd in ['payto', 'mktx']:
242 amount = int( 100000000 * Decimal(args[2]) )
244 label = ' '.join(args[3:])
246 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
252 if cmd not in offline_commands and not options.offline:
253 WalletSynchronizer(wallet).start()
257 # check if --from_addr not in wallet (for mktx/payto)
260 if options.from_addr:
261 from_addr = options.from_addr
262 if from_addr not in wallet.all_addresses():
265 # commands needing password
266 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
267 password = getpass.getpass('Password:') if wallet.use_encryption and not is_temporary else None
270 wallet.pw_decode( wallet.seed, password)
272 print "invalid password"
278 wallet.import_key(keypair,password)
280 print "keypair imported"
281 except BaseException, e:
282 print( 'Error:' + str(e) )
286 if cmd2 not in known_commands:
287 print "type 'electrum help <command>' to see the help for a specific command"
288 print "type 'electrum --help' to see the list of options"
289 print "list of commands:", ', '.join(known_commands)
291 print known_commands[cmd2]
294 seed = wallet.pw_decode( wallet.seed, password)
295 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
297 elif cmd == 'deseed':
299 print "Error: This wallet has no seed"
300 elif wallet.use_encryption:
301 print "Error: This wallet is encrypted"
303 ns = wallet.path + '.seed'
304 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
305 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
307 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
310 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
314 print "Action canceled."
316 elif cmd == 'reseed':
318 print "This wallet already has a seed", wallet.seed
320 ns = wallet.path + '.seed'
326 print "seed file not found"
330 d = ast.literal_eval( data )
332 imported_keys = d.get('imported_keys',{})
334 print "error with seed file"
337 mpk = wallet.master_public_key
339 wallet.imported_keys = imported_keys
340 wallet.use_encryption = False
341 wallet.init_mpk(seed)
342 if mpk == wallet.master_public_key:
344 print "Done: " + wallet.path
346 print "error: master public key does not match"
348 elif cmd == 'validateaddress':
350 print wallet.is_valid(addr)
352 elif cmd == 'balance':
358 c, u = wallet.get_balance()
360 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
362 print Decimal( c ) / 100000000
365 c, u = wallet.get_addr_balance(addr)
367 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
369 print "%s %s" % (addr, str(Decimal(c)/100000000))
371 elif cmd in [ 'contacts']:
372 for addr in wallet.addressbook:
373 print addr, " ", wallet.labels.get(addr)
379 elif cmd in [ 'addresses']:
380 for addr in wallet.all_addresses():
381 if options.show_all or not wallet.is_change(addr):
383 flags = wallet.get_address_flags(addr)
384 label = wallet.labels.get(addr,'')
386 if label: label = "\"%s\""%label
388 if options.show_balance:
389 h = wallet.history.get(addr,[])
392 # if item['is_input']: ni += 1
394 b = format_satoshis(wallet.get_addr_balance(addr)[0])
397 if options.show_keys:
398 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
399 print flags, m_addr, b, label
402 lines = wallet.get_tx_history()
409 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
411 print line['timestamp']
413 label = line.get('label')
414 if not label: label = line['tx_hash']
415 else: label = label + ' '*(64 - len(label) )
417 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
418 print "# balance: ", format_satoshis(b)
423 label = ' '.join(args[2:])
425 print "syntax: label <tx_hash> <text>"
427 wallet.labels[tx] = label
430 elif cmd in ['payto', 'mktx']:
431 if from_addr and is_temporary:
432 if from_addr.find(":") == -1:
433 keypair = from_addr + ":" + getpass.getpass('Private key:')
436 from_addr = keypair.split(':')[0]
437 if not wallet.import_key(keypair,password):
438 print "invalid key pair"
440 wallet.history[from_addr] = interface.retrieve_history(from_addr)
441 wallet.update_tx_history()
442 change_addr = from_addr
444 if options.change_addr:
445 change_addr = options.change_addr
447 for k, v in wallet.labels.items():
450 print "alias", to_address
452 if change_addr and v == change_addr:
455 tx = wallet.mktx( to_address, amount, label, password,
456 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
459 traceback.print_exc(file=sys.stdout)
462 if tx and cmd=='payto':
463 r, h = wallet.sendtx( tx )
469 wallet.imported_keys.pop(from_addr)
470 del(wallet.history[from_addr])
473 elif cmd == 'sendtx':
475 r, h = wallet.sendtx( tx )
478 elif cmd == 'password':
480 seed = wallet.pw_decode( wallet.seed, password)
484 new_password = getpass.getpass('New password:')
485 if new_password == getpass.getpass('Confirm new password:'):
486 wallet.update_password(seed, password, new_password)
488 print "error: mismatch"
490 elif cmd == 'signmessage':
492 message = ' '.join(args[2:])
494 print "Warning: Message was reconstructed from several arguments:", repr(message)
495 print wallet.sign_message(address, message, password)
497 elif cmd == 'verifymessage':
500 message = ' '.join(args[3:])
502 print "Warning: Message was reconstructed from several arguments:", repr(message)
504 wallet.verify_message(address, signature, message)
509 elif cmd == 'freeze':
511 print self.wallet.freeze(addr)
513 elif cmd == 'unfreeze':
515 print self.wallet.unfreeze(addr)
517 elif cmd == 'prioritize':
519 print self.wallet.prioritize(addr)
521 elif cmd == 'unprioritize':
523 print self.wallet.unprioritize(addr)