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
42 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
44 from decimal import Decimal
45 from lib import SimpleConfig
48 'help':'Prints this help',
49 'validateaddress':'Check that the address is valid',
50 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
51 'contacts': "Show your list of contacts",
52 'create':'Create a wallet',
53 'restore':'Restore a wallet',
54 'payto':"""Create and broadcast a transaction.
55 Syntax: payto <recipient> <amount> [label]
56 <recipient> can be a bitcoin address or a label
57 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
60 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
62 "Changes your password",
64 """Shows your list of addresses.
66 -a: show all addresses, including change addresses
68 -b: show the balance of addresses""",
70 'history':"Shows the transaction history",
71 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
73 """Create a signed transaction, password protected.
74 Syntax: mktx <recipient> <amount> [label]
75 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
78 "Print the generation seed of your wallet.",
80 'Imports a key pair\nSyntax: import <address>:<privatekey>',
82 '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 "',
84 '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 "',
86 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
88 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
90 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
99 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
101 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
103 if __name__ == '__main__':
105 # Load simple config class
106 simple_config = SimpleConfig()
108 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
109 parser = optparse.OptionParser(prog=usage)
110 parser.add_option("-g", "--gui", dest="gui", default=simple_config.config["gui"], help="gui")
111 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
112 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
113 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
114 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
115 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
116 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
117 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.")
118 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")
119 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
120 options, args = parser.parse_args()
124 wallet.set_path(options.wallet_path)
126 wallet.remote_url = options.remote_url
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)
166 error_message = QErrorMessage()
167 error_message.setFixedSize(350,200)
168 error_message.showMessage("<p>Sorry, the Electrum 'Lite GUI' requires Qt >= 4.7 to run. The pro GUI will be started instead.</p><p>Check your distributions packages for upgrades.</p>")
169 simple_config.config["gui"] = "qt"
170 simple_config.save_config
174 import lib.gui_qt as gui
176 import electrum.gui_qt as gui
178 #use the lite version if no toolkit specified
180 import lib.gui_lite as gui
182 import electrum.gui_lite as gui
184 sys.exit("Error: Unknown GUI: " + options.gui)
186 gui = gui.ElectrumGui(wallet)
187 interface = WalletSynchronizer(wallet, True, gui.server_list_changed)
191 found = wallet.file_exists
193 found = gui.restore_or_create()
194 except SystemExit, e:
196 except BaseException, e:
198 traceback.print_exc(file=sys.stdout)
199 #gui.show_message(e.message)
208 if cmd not in known_commands:
211 if not wallet.file_exists and cmd not in ['help','create','restore']:
212 print "Error: Wallet file not found."
213 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
216 if cmd in ['create', 'restore']:
217 if wallet.file_exists:
218 sys.exit("Error: Remove the existing wallet first!")
219 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
221 w_host, w_port, w_protocol = wallet.server.split(':')
222 host = raw_input("server (default:%s):"%w_host)
223 port = raw_input("port (default:%s):"%w_port)
224 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
225 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
226 gap = raw_input("gap limit (default 5):")
227 if host: w_host = host
228 if port: w_port = port
229 if protocol: w_protocol = protocol
230 wallet.server = w_host + ':' + w_port + ':' +w_protocol
231 if fee: wallet.fee = float(fee)
232 if gap: wallet.gap_limit = int(gap)
235 seed = raw_input("seed:")
239 print_error("Warning: Not hex, trying decode.")
240 seed = mnemonic.mn_decode( seed.split(' ') )
242 sys.exit("Error: No seed")
244 wallet.seed = str(seed)
245 wallet.init_mpk( wallet.seed )
246 if not options.offline:
247 WalletSynchronizer(wallet).start()
248 print "Recovering wallet..."
249 wallet.up_to_date_event.clear()
250 wallet.up_to_date = False
252 if wallet.is_found():
253 print "Recovery successful"
255 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():
300 # commands needing password
301 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
302 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
305 wallet.pw_decode( wallet.seed, password)
307 print_error("Error: This password does not decode this wallet.")
311 # See if they specificed a key on the cmd line, if not prompt
315 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
317 wallet.import_key(keypair,password)
319 print "Keypair imported"
320 except BaseException, e:
321 print_error("Error: Keypair import failed: " + str(e))
325 if cmd2 not in known_commands:
327 print "Type 'electrum help <command>' to see the help for a specific command"
328 print "Type 'electrum --help' to see the list of options"
329 print "List of commands:", ', '.join(known_commands)
331 print known_commands[cmd2]
334 seed = wallet.pw_decode( wallet.seed, password)
335 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
337 elif cmd == 'deseed':
339 print_error("Error: This wallet has no seed")
340 elif wallet.use_encryption:
341 print_error("Error: This wallet is encrypted")
343 ns = wallet.path + '.seed'
344 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
345 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
347 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
350 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
354 print_error("Action canceled.")
356 elif cmd == 'reseed':
358 print "Warning: This wallet already has a seed", wallet.seed
360 ns = wallet.path + '.seed'
366 sys.exit("Error: Seed file not found")
369 d = ast.literal_eval( data )
371 imported_keys = d.get('imported_keys',{})
373 sys.exit("Error: Error with seed file")
375 mpk = wallet.master_public_key
377 wallet.imported_keys = imported_keys
378 wallet.use_encryption = False
379 wallet.init_mpk(seed)
380 if mpk == wallet.master_public_key:
382 print "Done: " + wallet.path
384 print_error("Error: Master public key does not match")
386 elif cmd == 'validateaddress':
388 print wallet.is_valid(addr)
390 elif cmd == 'balance':
396 c, u = wallet.get_balance()
398 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
400 print Decimal( c ) / 100000000
403 c, u = wallet.get_addr_balance(addr)
405 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
407 print "%s %s" % (addr, str(Decimal(c)/100000000))
409 elif cmd in [ 'contacts']:
410 for addr in wallet.addressbook:
411 print addr, " ", wallet.labels.get(addr)
417 elif cmd in [ 'addresses']:
418 for addr in wallet.all_addresses():
419 if options.show_all or not wallet.is_change(addr):
421 flags = wallet.get_address_flags(addr)
422 label = wallet.labels.get(addr,'')
424 if label: label = "\"%s\""%label
426 if options.show_balance:
427 h = wallet.history.get(addr,[])
430 # if item['is_input']: ni += 1
432 b = format_satoshis(wallet.get_addr_balance(addr)[0])
435 if options.show_keys:
436 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
437 print flags, m_addr, b, label
440 lines = wallet.get_tx_history()
447 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
449 print line['timestamp']
451 label = line.get('label')
452 if not label: label = line['tx_hash']
453 else: label = label + ' '*(64 - len(label) )
455 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
456 print "# balance: ", format_satoshis(b)
461 label = ' '.join(args[2:])
463 print_error("Error. Syntax: label <tx_hash> <text>")
465 wallet.labels[tx] = label
468 elif cmd in ['payto', 'mktx']:
469 if from_addr and is_temporary:
470 if from_addr.find(":") == -1:
471 keypair = from_addr + ":" + prompt_password('Private key:', False)
474 from_addr = keypair.split(':')[0]
475 if not wallet.import_key(keypair,password):
476 print_error("Error: Invalid key pair")
478 wallet.history[from_addr] = interface.retrieve_history(from_addr)
479 wallet.update_tx_history()
480 change_addr = from_addr
482 if options.change_addr:
483 change_addr = options.change_addr
485 for k, v in wallet.labels.items():
488 print "alias", to_address
490 if change_addr and v == change_addr:
493 tx = wallet.mktx( to_address, amount, label, password,
494 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
497 traceback.print_exc(file=sys.stdout)
500 if tx and cmd=='payto':
501 r, h = wallet.sendtx( tx )
507 wallet.imported_keys.pop(from_addr)
508 del(wallet.history[from_addr])
511 elif cmd == 'sendtx':
513 r, h = wallet.sendtx( tx )
516 elif cmd == 'password':
518 seed = wallet.pw_decode( wallet.seed, password)
520 sys.exit("Error: Password does not decrypt this wallet.")
522 new_password = prompt_password('New password:')
523 wallet.update_password(seed, password, new_password)
525 elif cmd == 'signmessage':
527 print_error("Error: Invalid usage of signmessage.")
528 print known_commands[cmd]
531 message = ' '.join(args[2:])
533 print "Warning: Message was reconstructed from several arguments:", repr(message)
534 print wallet.sign_message(address, message, password)
536 elif cmd == 'verifymessage':
540 message = ' '.join(args[3:])
542 print_error("Error: Not all parameters were given, displaying help instead.")
543 print known_commands[cmd]
546 print "Warning: Message was reconstructed from several arguments:", repr(message)
548 wallet.verify_message(address, signature, message)
550 except BaseException as e:
551 print "Verification error: {0}".format(e)
554 elif cmd == 'freeze':
556 print self.wallet.freeze(addr)
558 elif cmd == 'unfreeze':
560 print self.wallet.unfreeze(addr)
562 elif cmd == 'prioritize':
564 print self.wallet.prioritize(addr)
566 elif cmd == 'unprioritize':
568 print self.wallet.unprioritize(addr)