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()
122 options.proxy = parse_proxy_options(options.proxy)
125 wallet.set_path(options.wallet_path)
131 elif len(args)==1 and re.match('^bitcoin:', args[0]):
136 firstarg = args[1] if len(args) > 1 else ''
138 #this entire if/else block is just concerned with importing the
139 #right GUI toolkit based the GUI command line option given
142 if options.gui=='gtk':
144 import lib.gui as gui
146 import electrum.gui as gui
147 elif options.gui=='qt':
149 import lib.gui_qt as gui
151 import electrum.gui_qt as gui
152 elif options.gui == 'lite':
153 # Let's do some dep checking and handle missing ones gracefully
155 from PyQt4.QtCore import *
156 from PyQt4.QtGui import *
158 print "You need to have PyQT installed to run Electrum in graphical mode."
159 print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
162 qtVersion = qVersion()
163 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
164 app = QApplication(sys.argv)
165 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.")
167 simple_config.set_key("gui", "qt")
170 import lib.gui_qt as gui
172 import electrum.gui_qt as gui
174 #use the lite version if no toolkit specified
176 import lib.gui_lite as gui
178 import electrum.gui_lite as gui
180 sys.exit("Error: Unknown GUI: " + options.gui)
182 gui = gui.ElectrumGui(wallet)
183 interface = WalletSynchronizer(wallet, True, gui.server_list_changed, options.proxy)
187 found = wallet.file_exists
189 found = gui.restore_or_create()
190 except SystemExit, e:
192 except BaseException, e:
194 traceback.print_exc(file=sys.stdout)
195 #gui.show_message(e.message)
204 if cmd not in known_commands:
207 if not wallet.file_exists and cmd not in ['help','create','restore']:
208 print "Error: Wallet file not found."
209 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
212 if cmd in ['create', 'restore']:
213 if wallet.file_exists:
214 sys.exit("Error: Remove the existing wallet first!")
215 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
217 w_host, w_port, w_protocol = wallet.server.split(':')
218 host = raw_input("server (default:%s):"%w_host)
219 port = raw_input("port (default:%s):"%w_port)
220 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
221 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
222 gap = raw_input("gap limit (default 5):")
223 if host: w_host = host
224 if port: w_port = port
225 if protocol: w_protocol = protocol
226 wallet.server = w_host + ':' + w_port + ':' +w_protocol
227 if fee: wallet.fee = float(fee)
228 if gap: wallet.gap_limit = int(gap)
231 seed = raw_input("seed:")
235 print_error("Warning: Not hex, trying decode.")
236 seed = mnemonic.mn_decode( seed.split(' ') )
238 sys.exit("Error: No seed")
240 wallet.seed = str(seed)
241 wallet.init_mpk( wallet.seed )
242 if not options.offline:
243 WalletSynchronizer(wallet).start()
244 print "Recovering wallet..."
245 wallet.up_to_date_event.clear()
246 wallet.up_to_date = False
248 if wallet.is_found():
249 print "Recovery successful"
251 print_error("Warning: Found no history for this wallet")
254 wallet.fill_addressbook()
256 print_error("Wallet saved in '" + wallet.path)
258 wallet.new_seed(None)
259 wallet.init_mpk( wallet.seed )
260 wallet.synchronize() # there is no wallet thread
262 print "Your wallet generation seed is: " + wallet.seed
263 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
264 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
265 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
266 print "Wallet saved in '%s'"%wallet.path
269 wallet.update_password(wallet.seed, None, password)
272 if cmd in ['payto', 'mktx']:
275 amount = int( 100000000 * Decimal(args[2]) )
277 label = ' '.join(args[3:])
279 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
285 if cmd not in offline_commands and not options.offline:
286 WalletSynchronizer(wallet).start()
290 # check if --from_addr not in wallet (for mktx/payto)
293 if options.from_addr:
294 from_addr = options.from_addr
295 if from_addr not in wallet.all_addresses():
299 if cmd=='addresses' and options.show_keys:
300 print "WARNING: ALL your private keys are secret."
301 print "Exposing a single private key can compromise your entire wallet!"
302 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
304 # commands needing password
305 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
306 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
309 wallet.pw_decode( wallet.seed, password)
311 print_error("Error: This password does not decode this wallet.")
315 # See if they specificed a key on the cmd line, if not prompt
319 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
321 wallet.import_key(keypair,password)
323 print "Keypair imported"
324 except BaseException, e:
325 print_error("Error: Keypair import failed: " + str(e))
329 if cmd2 not in known_commands:
331 print "Type 'electrum help <command>' to see the help for a specific command"
332 print "Type 'electrum --help' to see the list of options"
333 print "List of commands:", ', '.join(known_commands)
335 print known_commands[cmd2]
338 seed = wallet.pw_decode( wallet.seed, password)
339 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
341 elif cmd == 'deseed':
343 print_error("Error: This wallet has no seed")
344 elif wallet.use_encryption:
345 print_error("Error: This wallet is encrypted")
347 ns = wallet.path + '.seed'
348 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
349 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
351 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
354 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
358 print_error("Action canceled.")
360 elif cmd == 'reseed':
362 print "Warning: This wallet already has a seed", wallet.seed
364 ns = wallet.path + '.seed'
370 sys.exit("Error: Seed file not found")
373 d = ast.literal_eval( data )
375 imported_keys = d.get('imported_keys',{})
377 sys.exit("Error: Error with seed file")
379 mpk = wallet.master_public_key
381 wallet.imported_keys = imported_keys
382 wallet.use_encryption = False
383 wallet.init_mpk(seed)
384 if mpk == wallet.master_public_key:
386 print "Done: " + wallet.path
388 print_error("Error: Master public key does not match")
390 elif cmd == 'validateaddress':
392 print wallet.is_valid(addr)
394 elif cmd == 'balance':
400 c, u = wallet.get_balance()
402 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
404 print Decimal( c ) / 100000000
407 c, u = wallet.get_addr_balance(addr)
409 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
411 print "%s %s" % (addr, str(Decimal(c)/100000000))
413 elif cmd in [ 'contacts']:
414 for addr in wallet.addressbook:
415 print addr, " ", wallet.labels.get(addr)
421 elif cmd in [ 'addresses']:
422 for addr in wallet.all_addresses():
423 if options.show_all or not wallet.is_change(addr):
425 flags = wallet.get_address_flags(addr)
426 label = wallet.labels.get(addr,'')
428 if label: label = "\"%s\""%label
430 if options.show_balance:
431 h = wallet.history.get(addr,[])
434 # if item['is_input']: ni += 1
436 b = format_satoshis(wallet.get_addr_balance(addr)[0])
439 if options.show_keys:
440 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
441 print flags, m_addr, b, label
444 lines = wallet.get_tx_history()
451 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
453 print line['timestamp']
455 label = line.get('label')
456 if not label: label = line['tx_hash']
457 else: label = label + ' '*(64 - len(label) )
459 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
460 print "# balance: ", format_satoshis(b)
465 label = ' '.join(args[2:])
467 print_error("Error. Syntax: label <tx_hash> <text>")
469 wallet.labels[tx] = label
472 elif cmd in ['payto', 'mktx']:
473 if from_addr and is_temporary:
474 if from_addr.find(":") == -1:
475 keypair = from_addr + ":" + prompt_password('Private key:', False)
478 from_addr = keypair.split(':')[0]
479 if not wallet.import_key(keypair,password):
480 print_error("Error: Invalid key pair")
482 wallet.history[from_addr] = interface.retrieve_history(from_addr)
483 wallet.update_tx_history()
484 change_addr = from_addr
486 if options.change_addr:
487 change_addr = options.change_addr
489 for k, v in wallet.labels.items():
492 print "alias", to_address
494 if change_addr and v == change_addr:
497 tx = wallet.mktx( to_address, amount, label, password,
498 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
501 traceback.print_exc(file=sys.stdout)
504 if tx and cmd=='payto':
505 r, h = wallet.sendtx( tx )
511 wallet.imported_keys.pop(from_addr)
512 del(wallet.history[from_addr])
515 elif cmd == 'sendtx':
517 r, h = wallet.sendtx( tx )
520 elif cmd == 'password':
522 seed = wallet.pw_decode( wallet.seed, password)
524 sys.exit("Error: Password does not decrypt this wallet.")
526 new_password = prompt_password('New password:')
527 wallet.update_password(seed, password, new_password)
529 elif cmd == 'signmessage':
531 print_error("Error: Invalid usage of signmessage.")
532 print known_commands[cmd]
535 message = ' '.join(args[2:])
537 print "Warning: Message was reconstructed from several arguments:", repr(message)
538 print wallet.sign_message(address, message, password)
540 elif cmd == 'verifymessage':
544 message = ' '.join(args[3:])
546 print_error("Error: Not all parameters were given, displaying help instead.")
547 print known_commands[cmd]
550 print "Warning: Message was reconstructed from several arguments:", repr(message)
552 wallet.verify_message(address, signature, message)
554 except BaseException as e:
555 print "Verification error: {0}".format(e)
558 elif cmd == 'freeze':
560 print self.wallet.freeze(addr)
562 elif cmd == 'unfreeze':
564 print self.wallet.unfreeze(addr)
566 elif cmd == 'prioritize':
568 print self.wallet.prioritize(addr)
570 elif cmd == 'unprioritize':
572 print self.wallet.unprioritize(addr)