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("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
119 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
120 options, args = parser.parse_args()
123 options.proxy = parse_proxy_options(options.proxy)
126 wallet.set_path(options.wallet_path)
128 wallet.remote_url = options.remote_url
133 elif len(args)==1 and re.match('^bitcoin:', args[0]):
138 firstarg = args[1] if len(args) > 1 else ''
140 #this entire if/else block is just concerned with importing the
141 #right GUI toolkit based the GUI command line option given
144 if options.gui=='gtk':
146 import lib.gui as gui
148 import electrum.gui as gui
149 elif options.gui=='qt':
151 import lib.gui_qt as gui
153 import electrum.gui_qt as gui
154 elif options.gui == 'lite':
155 # Let's do some dep checking and handle missing ones gracefully
157 from PyQt4.QtCore import *
158 from PyQt4.QtGui import *
160 print "You need to have PyQT installed to run Electrum in graphical mode."
161 print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
164 qtVersion = qVersion()
165 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
166 app = QApplication(sys.argv)
167 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.")
169 simple_config.set_key("gui", "qt")
172 import lib.gui_qt as gui
174 import electrum.gui_qt as gui
176 #use the lite version if no toolkit specified
178 import lib.gui_lite as gui
180 import electrum.gui_lite as gui
182 sys.exit("Error: Unknown GUI: " + options.gui)
184 gui = gui.ElectrumGui(wallet)
185 interface = WalletSynchronizer(wallet, True, gui.server_list_changed, options.proxy)
189 found = wallet.file_exists
191 found = gui.restore_or_create()
192 except SystemExit, e:
194 except BaseException, e:
196 traceback.print_exc(file=sys.stdout)
197 #gui.show_message(e.message)
206 if cmd not in known_commands:
209 if not wallet.file_exists and cmd not in ['help','create','restore']:
210 print "Error: Wallet file not found."
211 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
214 if cmd in ['create', 'restore']:
215 if wallet.file_exists:
216 sys.exit("Error: Remove the existing wallet first!")
217 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
219 w_host, w_port, w_protocol = wallet.server.split(':')
220 host = raw_input("server (default:%s):"%w_host)
221 port = raw_input("port (default:%s):"%w_port)
222 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
223 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
224 gap = raw_input("gap limit (default 5):")
225 if host: w_host = host
226 if port: w_port = port
227 if protocol: w_protocol = protocol
228 wallet.server = w_host + ':' + w_port + ':' +w_protocol
229 if fee: wallet.fee = float(fee)
230 if gap: wallet.gap_limit = int(gap)
233 seed = raw_input("seed:")
237 print_error("Warning: Not hex, trying decode.")
238 seed = mnemonic.mn_decode( seed.split(' ') )
240 sys.exit("Error: No seed")
242 wallet.seed = str(seed)
243 wallet.init_mpk( wallet.seed )
244 if not options.offline:
245 WalletSynchronizer(wallet).start()
246 print "Recovering wallet..."
247 wallet.up_to_date_event.clear()
248 wallet.up_to_date = False
250 if wallet.is_found():
251 print "Recovery successful"
253 print_error("Warning: Found no history for this wallet")
256 wallet.fill_addressbook()
258 print_error("Wallet saved in '" + wallet.path)
260 wallet.new_seed(None)
261 wallet.init_mpk( wallet.seed )
262 wallet.synchronize() # there is no wallet thread
264 print "Your wallet generation seed is: " + wallet.seed
265 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
266 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
267 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
268 print "Wallet saved in '%s'"%wallet.path
271 wallet.update_password(wallet.seed, None, password)
274 if cmd in ['payto', 'mktx']:
277 amount = int( 100000000 * Decimal(args[2]) )
279 label = ' '.join(args[3:])
281 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
287 if cmd not in offline_commands and not options.offline:
288 WalletSynchronizer(wallet).start()
292 # check if --from_addr not in wallet (for mktx/payto)
295 if options.from_addr:
296 from_addr = options.from_addr
297 if from_addr not in wallet.all_addresses():
301 if cmd=='addresses' and options.show_keys:
302 print "WARNING: ALL your private keys are secret."
303 print "Exposing a single private key can compromise your entire wallet!"
304 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
306 # commands needing password
307 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
308 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
311 wallet.pw_decode( wallet.seed, password)
313 print_error("Error: This password does not decode this wallet.")
317 # See if they specificed a key on the cmd line, if not prompt
321 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
323 wallet.import_key(keypair,password)
325 print "Keypair imported"
326 except BaseException, e:
327 print_error("Error: Keypair import failed: " + str(e))
331 if cmd2 not in known_commands:
333 print "Type 'electrum help <command>' to see the help for a specific command"
334 print "Type 'electrum --help' to see the list of options"
335 print "List of commands:", ', '.join(known_commands)
337 print known_commands[cmd2]
340 seed = wallet.pw_decode( wallet.seed, password)
341 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
343 elif cmd == 'deseed':
345 print_error("Error: This wallet has no seed")
346 elif wallet.use_encryption:
347 print_error("Error: This wallet is encrypted")
349 ns = wallet.path + '.seed'
350 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
351 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
353 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
356 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
360 print_error("Action canceled.")
362 elif cmd == 'reseed':
364 print "Warning: This wallet already has a seed", wallet.seed
366 ns = wallet.path + '.seed'
372 sys.exit("Error: Seed file not found")
375 d = ast.literal_eval( data )
377 imported_keys = d.get('imported_keys',{})
379 sys.exit("Error: Error with seed file")
381 mpk = wallet.master_public_key
383 wallet.imported_keys = imported_keys
384 wallet.use_encryption = False
385 wallet.init_mpk(seed)
386 if mpk == wallet.master_public_key:
388 print "Done: " + wallet.path
390 print_error("Error: Master public key does not match")
392 elif cmd == 'validateaddress':
394 print wallet.is_valid(addr)
396 elif cmd == 'balance':
402 c, u = wallet.get_balance()
404 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
406 print Decimal( c ) / 100000000
409 c, u = wallet.get_addr_balance(addr)
411 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
413 print "%s %s" % (addr, str(Decimal(c)/100000000))
415 elif cmd in [ 'contacts']:
416 for addr in wallet.addressbook:
417 print addr, " ", wallet.labels.get(addr)
423 elif cmd in [ 'addresses']:
424 for addr in wallet.all_addresses():
425 if options.show_all or not wallet.is_change(addr):
427 flags = wallet.get_address_flags(addr)
428 label = wallet.labels.get(addr,'')
430 if label: label = "\"%s\""%label
432 if options.show_balance:
433 h = wallet.history.get(addr,[])
436 # if item['is_input']: ni += 1
438 b = format_satoshis(wallet.get_addr_balance(addr)[0])
441 if options.show_keys:
442 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
443 print flags, m_addr, b, label
446 lines = wallet.get_tx_history()
453 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
455 print line['timestamp']
457 label = line.get('label')
458 if not label: label = line['tx_hash']
459 else: label = label + ' '*(64 - len(label) )
461 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
462 print "# balance: ", format_satoshis(b)
467 label = ' '.join(args[2:])
469 print_error("Error. Syntax: label <tx_hash> <text>")
471 wallet.labels[tx] = label
474 elif cmd in ['payto', 'mktx']:
475 if from_addr and is_temporary:
476 if from_addr.find(":") == -1:
477 keypair = from_addr + ":" + prompt_password('Private key:', False)
480 from_addr = keypair.split(':')[0]
481 if not wallet.import_key(keypair,password):
482 print_error("Error: Invalid key pair")
484 wallet.history[from_addr] = interface.retrieve_history(from_addr)
485 wallet.update_tx_history()
486 change_addr = from_addr
488 if options.change_addr:
489 change_addr = options.change_addr
491 for k, v in wallet.labels.items():
494 print "alias", to_address
496 if change_addr and v == change_addr:
499 tx = wallet.mktx( to_address, amount, label, password,
500 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
503 traceback.print_exc(file=sys.stdout)
506 if tx and cmd=='payto':
507 r, h = wallet.sendtx( tx )
513 wallet.imported_keys.pop(from_addr)
514 del(wallet.history[from_addr])
517 elif cmd == 'sendtx':
519 r, h = wallet.sendtx( tx )
522 elif cmd == 'password':
524 seed = wallet.pw_decode( wallet.seed, password)
526 sys.exit("Error: Password does not decrypt this wallet.")
528 new_password = prompt_password('New password:')
529 wallet.update_password(seed, password, new_password)
531 elif cmd == 'signmessage':
533 print_error("Error: Invalid usage of signmessage.")
534 print known_commands[cmd]
537 message = ' '.join(args[2:])
539 print "Warning: Message was reconstructed from several arguments:", repr(message)
540 print wallet.sign_message(address, message, password)
542 elif cmd == 'verifymessage':
546 message = ' '.join(args[3:])
548 print_error("Error: Not all parameters were given, displaying help instead.")
549 print known_commands[cmd]
552 print "Warning: Message was reconstructed from several arguments:", repr(message)
554 wallet.verify_message(address, signature, message)
556 except BaseException as e:
557 print "Verification error: {0}".format(e)
560 elif cmd == 'freeze':
562 print wallet.freeze(addr)
564 elif cmd == 'unfreeze':
566 print wallet.unfreeze(addr)
568 elif cmd == 'prioritize':
570 print wallet.prioritize(addr)
572 elif cmd == 'unprioritize':
574 print wallet.unprioritize(addr)