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/>.
25 from lib.util import print_error
27 from electrum.util import print_error
32 sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
37 sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
40 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, parse_proxy_options, SimpleConfig
42 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, parse_proxy_options, SimpleConfig
44 from decimal import Decimal
47 'help':'Prints this help',
48 'validateaddress':'Check that the address is valid',
49 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
50 'contacts': "Show your list of contacts",
51 'create':'Create a wallet',
52 'restore':'Restore a wallet',
53 'payto':"""Create and broadcast a transaction.
54 Syntax: payto <recipient> <amount> [label]
55 <recipient> can be a bitcoin address or a label
56 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
59 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
61 "Changes your password",
63 """Shows your list of addresses.
65 -a: show all addresses, including change addresses
67 -b: show the balance of addresses""",
69 'history':"Shows the transaction history",
70 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
72 """Create a signed transaction, password protected.
73 Syntax: mktx <recipient> <amount> [label]
74 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
77 "Print the generation seed of your wallet.",
79 'Imports a key pair\nSyntax: import <address>:<privatekey>',
81 'Signs a message with a key\nSyntax: signmessage <address> <message>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello This is a weird String "',
83 'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello This is a weird String "',
85 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
87 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
89 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
98 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
100 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
102 if __name__ == '__main__':
104 # Load simple config class
105 simple_config = SimpleConfig()
107 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
108 parser = optparse.OptionParser(prog=usage)
109 parser.add_option("-g", "--gui", dest="gui", default=simple_config.config["gui"], help="gui")
110 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
111 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
112 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
113 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
114 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
115 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
116 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.")
117 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")
118 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
119 options, args = parser.parse_args()
121 proxy = parse_proxy_options(options.proxy) if options.proxy else simple_config.config["proxy"]
123 wallet.set_path(options.wallet_path)
129 elif len(args)==1 and re.match('^bitcoin:', args[0]):
134 firstarg = args[1] if len(args) > 1 else ''
136 #this entire if/else block is just concerned with importing the
137 #right GUI toolkit based the GUI command line option given
140 if options.gui=='gtk':
142 import lib.gui as gui
144 import electrum.gui as gui
145 elif options.gui=='qt':
147 import lib.gui_qt as gui
149 import electrum.gui_qt as gui
150 elif options.gui == 'lite':
151 # Let's do some dep checking and handle missing ones gracefully
153 from PyQt4.QtCore import *
154 from PyQt4.QtGui import *
156 print "You need to have PyQT installed to run Electrum in graphical mode."
157 print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
160 qtVersion = qVersion()
161 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
162 app = QApplication(sys.argv)
163 QMessageBox.warning(None,"Could not start Lite GUI.", "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nElectrum is now setup to load the Pro GUI.")
165 simple_config.set_key("gui", "qt")
168 import lib.gui_qt as gui
170 import electrum.gui_qt as gui
172 #use the lite version if no toolkit specified
174 import lib.gui_lite as gui
176 import electrum.gui_lite as gui
178 sys.exit("Error: Unknown GUI: " + options.gui)
180 gui = gui.ElectrumGui(wallet)
181 interface = WalletSynchronizer(wallet, True, gui.server_list_changed, proxy)
185 found = wallet.file_exists
187 found = gui.restore_or_create()
188 except SystemExit, e:
190 except BaseException, e:
192 traceback.print_exc(file=sys.stdout)
193 #gui.show_message(e.message)
202 if cmd not in known_commands:
205 if not wallet.file_exists and cmd not in ['help','create','restore']:
206 print "Error: Wallet file not found."
207 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
210 if cmd in ['create', 'restore']:
211 if wallet.file_exists:
212 sys.exit("Error: Remove the existing wallet first!")
213 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
215 w_host, w_port, w_protocol = wallet.server.split(':')
216 host = raw_input("server (default:%s):"%w_host)
217 port = raw_input("port (default:%s):"%w_port)
218 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
219 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
220 gap = raw_input("gap limit (default 5):")
221 if host: w_host = host
222 if port: w_port = port
223 if protocol: w_protocol = protocol
224 wallet.server = w_host + ':' + w_port + ':' +w_protocol
225 if fee: wallet.fee = float(fee)
226 if gap: wallet.gap_limit = int(gap)
229 seed = raw_input("seed:")
233 print_error("Warning: Not hex, trying decode.")
234 seed = mnemonic.mn_decode( seed.split(' ') )
236 sys.exit("Error: No seed")
238 wallet.seed = str(seed)
239 wallet.init_mpk( wallet.seed )
240 if not options.offline:
241 WalletSynchronizer(wallet).start()
242 print "Recovering wallet..."
243 wallet.up_to_date_event.clear()
244 wallet.up_to_date = False
246 if wallet.is_found():
247 print "Recovery successful"
249 print_error("Warning: Found no history for this wallet")
252 wallet.fill_addressbook()
254 print_error("Wallet saved in '" + wallet.path)
256 wallet.new_seed(None)
257 wallet.init_mpk( wallet.seed )
258 wallet.synchronize() # there is no wallet thread
260 print "Your wallet generation seed is: " + wallet.seed
261 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
262 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
263 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
264 print "Wallet saved in '%s'"%wallet.path
267 wallet.update_password(wallet.seed, None, password)
270 if cmd in ['payto', 'mktx']:
273 amount = int( 100000000 * Decimal(args[2]) )
275 label = ' '.join(args[3:])
277 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
283 if cmd not in offline_commands and not options.offline:
284 WalletSynchronizer(wallet).start()
288 # check if --from_addr not in wallet (for mktx/payto)
291 if options.from_addr:
292 from_addr = options.from_addr
293 if from_addr not in wallet.all_addresses():
297 if cmd=='addresses' and options.show_keys:
298 print "WARNING: ALL your private keys are secret."
299 print "Exposing a single private key can compromise your entire wallet!"
300 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
302 # commands needing password
303 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
304 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
307 wallet.pw_decode( wallet.seed, password)
309 print_error("Error: This password does not decode this wallet.")
313 # See if they specificed a key on the cmd line, if not prompt
317 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
319 wallet.import_key(keypair,password)
321 print "Keypair imported"
322 except BaseException, e:
323 print_error("Error: Keypair import failed: " + str(e))
327 if cmd2 not in known_commands:
329 print "Type 'electrum help <command>' to see the help for a specific command"
330 print "Type 'electrum --help' to see the list of options"
331 print "List of commands:", ', '.join(known_commands)
333 print known_commands[cmd2]
336 seed = wallet.pw_decode( wallet.seed, password)
337 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
339 elif cmd == 'deseed':
341 print_error("Error: This wallet has no seed")
342 elif wallet.use_encryption:
343 print_error("Error: This wallet is encrypted")
345 ns = wallet.path + '.seed'
346 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
347 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
349 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
352 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
356 print_error("Action canceled.")
358 elif cmd == 'reseed':
360 print "Warning: This wallet already has a seed", wallet.seed
362 ns = wallet.path + '.seed'
368 sys.exit("Error: Seed file not found")
371 d = ast.literal_eval( data )
373 imported_keys = d.get('imported_keys',{})
375 sys.exit("Error: Error with seed file")
377 mpk = wallet.master_public_key
379 wallet.imported_keys = imported_keys
380 wallet.use_encryption = False
381 wallet.init_mpk(seed)
382 if mpk == wallet.master_public_key:
384 print "Done: " + wallet.path
386 print_error("Error: Master public key does not match")
388 elif cmd == 'validateaddress':
390 print wallet.is_valid(addr)
392 elif cmd == 'balance':
398 c, u = wallet.get_balance()
400 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
402 print Decimal( c ) / 100000000
405 c, u = wallet.get_addr_balance(addr)
407 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
409 print "%s %s" % (addr, str(Decimal(c)/100000000))
411 elif cmd in [ 'contacts']:
412 for addr in wallet.addressbook:
413 print addr, " ", wallet.labels.get(addr)
419 elif cmd in [ 'addresses']:
420 for addr in wallet.all_addresses():
421 if options.show_all or not wallet.is_change(addr):
423 flags = wallet.get_address_flags(addr)
424 label = wallet.labels.get(addr,'')
426 if label: label = "\"%s\""%label
428 if options.show_balance:
429 h = wallet.history.get(addr,[])
432 # if item['is_input']: ni += 1
434 b = format_satoshis(wallet.get_addr_balance(addr)[0])
437 if options.show_keys:
438 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
439 print flags, m_addr, b, label
442 lines = wallet.get_tx_history()
449 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
451 print line['timestamp']
453 label = line.get('label')
454 if not label: label = line['tx_hash']
455 else: label = label + ' '*(64 - len(label) )
457 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
458 print "# balance: ", format_satoshis(b)
463 label = ' '.join(args[2:])
465 print_error("Error. Syntax: label <tx_hash> <text>")
467 wallet.labels[tx] = label
470 elif cmd in ['payto', 'mktx']:
471 if from_addr and is_temporary:
472 if from_addr.find(":") == -1:
473 keypair = from_addr + ":" + prompt_password('Private key:', False)
476 from_addr = keypair.split(':')[0]
477 if not wallet.import_key(keypair,password):
478 print_error("Error: Invalid key pair")
480 wallet.history[from_addr] = interface.retrieve_history(from_addr)
481 wallet.update_tx_history()
482 change_addr = from_addr
484 if options.change_addr:
485 change_addr = options.change_addr
487 for k, v in wallet.labels.items():
490 print "alias", to_address
492 if change_addr and v == change_addr:
495 tx = wallet.mktx( to_address, amount, label, password,
496 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
499 traceback.print_exc(file=sys.stdout)
502 if tx and cmd=='payto':
503 r, h = wallet.sendtx( tx )
509 wallet.imported_keys.pop(from_addr)
510 del(wallet.history[from_addr])
513 elif cmd == 'sendtx':
515 r, h = wallet.sendtx( tx )
518 elif cmd == 'password':
520 seed = wallet.pw_decode( wallet.seed, password)
522 sys.exit("Error: Password does not decrypt this wallet.")
524 new_password = prompt_password('New password:')
525 wallet.update_password(seed, password, new_password)
527 elif cmd == 'signmessage':
529 print_error("Error: Invalid usage of signmessage.")
530 print known_commands[cmd]
533 message = ' '.join(args[2:])
535 print "Warning: Message was reconstructed from several arguments:", repr(message)
536 print wallet.sign_message(address, message, password)
538 elif cmd == 'verifymessage':
542 message = ' '.join(args[3:])
544 print_error("Error: Not all parameters were given, displaying help instead.")
545 print known_commands[cmd]
548 print "Warning: Message was reconstructed from several arguments:", repr(message)
550 wallet.verify_message(address, signature, message)
552 except BaseException as e:
553 print "Verification error: {0}".format(e)
556 elif cmd == 'freeze':
558 print self.wallet.freeze(addr)
560 elif cmd == 'unfreeze':
562 print self.wallet.unfreeze(addr)
564 elif cmd == 'prioritize':
566 print self.wallet.prioritize(addr)
568 elif cmd == 'unprioritize':
570 print self.wallet.unprioritize(addr)