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
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 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
105 parser = optparse.OptionParser(prog=usage)
106 parser.add_option("-g", "--gui", dest="gui", default="lite", help="gui")
107 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
108 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
109 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
110 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
111 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
112 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
113 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.")
114 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")
115 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
116 options, args = parser.parse_args()
119 wallet.set_path(options.wallet_path)
121 wallet.remote_url = options.remote_url
126 elif len(args)==1 and re.match('^bitcoin:', args[0]):
131 firstarg = args[1] if len(args) > 1 else ''
133 #this entire if/else block is just concerned with importing the
134 #right GUI toolkit based the GUI command line option given
137 if options.gui=='gtk':
139 import lib.gui as gui
141 import electrum.gui as gui
142 elif options.gui=='qt':
144 import lib.gui_qt as gui
146 import electrum.gui_qt as gui
147 elif options.gui == 'lite':
148 # Let's do some dep checking and handle missing ones gracefully
150 from PyQt4.QtCore import *
151 from PyQt4.QtGui import *
153 print "You need to have PyQT installed to run Electrum in graphical mode."
154 print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
157 qtVersion = qVersion()
158 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
159 app = QApplication(sys.argv)
161 error_message = QErrorMessage()
162 error_message.setFixedSize(350,200)
163 error_message.showMessage("<p>Sorry, Electrum requires Qt >= 4.7 to run.</p><p>Check your distributions packages or download it at http://qt.nokia.com/downloads</p>")
167 #use the lite version if no toolkit specified
169 import lib.gui_lite as gui
171 import electrum.gui_lite as gui
173 sys.exit("Error: Unknown GUI: " + options.gui)
175 gui = gui.ElectrumGui(wallet)
176 interface = WalletSynchronizer(wallet, True, gui.server_list_changed)
180 found = wallet.file_exists
182 found = gui.restore_or_create()
183 except SystemExit, e:
185 except BaseException, e:
187 traceback.print_exc(file=sys.stdout)
188 #gui.show_message(e.message)
197 if cmd not in known_commands:
200 if not wallet.file_exists and cmd not in ['help','create','restore']:
201 print "Error: Wallet file not found."
202 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
205 if cmd in ['create', 'restore']:
206 if wallet.file_exists:
207 sys.exit("Error: Remove the existing wallet first!")
208 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
210 w_host, w_port, w_protocol = wallet.server.split(':')
211 host = raw_input("server (default:%s):"%w_host)
212 port = raw_input("port (default:%s):"%w_port)
213 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
214 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
215 gap = raw_input("gap limit (default 5):")
216 if host: w_host = host
217 if port: w_port = port
218 if protocol: w_protocol = protocol
219 wallet.server = w_host + ':' + w_port + ':' +w_protocol
220 if fee: wallet.fee = float(fee)
221 if gap: wallet.gap_limit = int(gap)
224 seed = raw_input("seed:")
228 print_error("Warning: Not hex, trying decode.")
229 seed = mnemonic.mn_decode( seed.split(' ') )
231 sys.exit("Error: No seed")
233 wallet.seed = str(seed)
234 wallet.init_mpk( wallet.seed )
235 if not options.offline:
236 WalletSynchronizer(wallet).start()
237 print "Recovering wallet..."
238 wallet.up_to_date_event.clear()
239 wallet.up_to_date = False
241 if wallet.is_found():
242 print "Recovery successful"
244 print_error("Warning: Found no history for this wallet")
245 wallet.fill_addressbook()
247 print_error("Wallet saved in '" + wallet.path)
249 wallet.new_seed(None)
250 wallet.init_mpk( wallet.seed )
251 wallet.synchronize() # there is no wallet thread
253 print "Your wallet generation seed is: " + wallet.seed
254 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
255 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
256 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
257 print "Wallet saved in '%s'"%wallet.path
260 wallet.update_password(wallet.seed, None, password)
263 if cmd in ['payto', 'mktx']:
266 amount = int( 100000000 * Decimal(args[2]) )
268 label = ' '.join(args[3:])
270 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
276 if cmd not in offline_commands and not options.offline:
277 WalletSynchronizer(wallet).start()
281 # check if --from_addr not in wallet (for mktx/payto)
284 if options.from_addr:
285 from_addr = options.from_addr
286 if from_addr not in wallet.all_addresses():
289 # commands needing password
290 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
291 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
294 wallet.pw_decode( wallet.seed, password)
296 print_error("Error: This password does not decode this wallet.")
300 # See if they specificed a key on the cmd line, if not prompt
304 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
306 wallet.import_key(keypair,password)
308 print "Keypair imported"
309 except BaseException, e:
310 print_error("Error: Keypair import failed: " + str(e))
314 if cmd2 not in known_commands:
316 print "Type 'electrum help <command>' to see the help for a specific command"
317 print "Type 'electrum --help' to see the list of options"
318 print "List of commands:", ', '.join(known_commands)
320 print known_commands[cmd2]
323 seed = wallet.pw_decode( wallet.seed, password)
324 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
326 elif cmd == 'deseed':
328 print_error("Error: This wallet has no seed")
329 elif wallet.use_encryption:
330 print_error("Error: This wallet is encrypted")
332 ns = wallet.path + '.seed'
333 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
334 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
336 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
339 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
343 print_error("Action canceled.")
345 elif cmd == 'reseed':
347 print "Warning: This wallet already has a seed", wallet.seed
349 ns = wallet.path + '.seed'
355 sys.exit("Error: Seed file not found")
358 d = ast.literal_eval( data )
360 imported_keys = d.get('imported_keys',{})
362 sys.exit("Error: Error with seed file")
364 mpk = wallet.master_public_key
366 wallet.imported_keys = imported_keys
367 wallet.use_encryption = False
368 wallet.init_mpk(seed)
369 if mpk == wallet.master_public_key:
371 print "Done: " + wallet.path
373 print_error("Error: Master public key does not match")
375 elif cmd == 'validateaddress':
377 print wallet.is_valid(addr)
379 elif cmd == 'balance':
385 c, u = wallet.get_balance()
387 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
389 print Decimal( c ) / 100000000
392 c, u = wallet.get_addr_balance(addr)
394 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
396 print "%s %s" % (addr, str(Decimal(c)/100000000))
398 elif cmd in [ 'contacts']:
399 for addr in wallet.addressbook:
400 print addr, " ", wallet.labels.get(addr)
406 elif cmd in [ 'addresses']:
407 for addr in wallet.all_addresses():
408 if options.show_all or not wallet.is_change(addr):
410 flags = wallet.get_address_flags(addr)
411 label = wallet.labels.get(addr,'')
413 if label: label = "\"%s\""%label
415 if options.show_balance:
416 h = wallet.history.get(addr,[])
419 # if item['is_input']: ni += 1
421 b = format_satoshis(wallet.get_addr_balance(addr)[0])
424 if options.show_keys:
425 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
426 print flags, m_addr, b, label
429 lines = wallet.get_tx_history()
436 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
438 print line['timestamp']
440 label = line.get('label')
441 if not label: label = line['tx_hash']
442 else: label = label + ' '*(64 - len(label) )
444 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
445 print "# balance: ", format_satoshis(b)
450 label = ' '.join(args[2:])
452 print_error("Error. Syntax: label <tx_hash> <text>")
454 wallet.labels[tx] = label
457 elif cmd in ['payto', 'mktx']:
458 if from_addr and is_temporary:
459 if from_addr.find(":") == -1:
460 keypair = from_addr + ":" + prompt_password('Private key:', False)
463 from_addr = keypair.split(':')[0]
464 if not wallet.import_key(keypair,password):
465 print_error("Error: Invalid key pair")
467 wallet.history[from_addr] = interface.retrieve_history(from_addr)
468 wallet.update_tx_history()
469 change_addr = from_addr
471 if options.change_addr:
472 change_addr = options.change_addr
474 for k, v in wallet.labels.items():
477 print "alias", to_address
479 if change_addr and v == change_addr:
482 tx = wallet.mktx( to_address, amount, label, password,
483 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
486 traceback.print_exc(file=sys.stdout)
489 if tx and cmd=='payto':
490 r, h = wallet.sendtx( tx )
496 wallet.imported_keys.pop(from_addr)
497 del(wallet.history[from_addr])
500 elif cmd == 'sendtx':
502 r, h = wallet.sendtx( tx )
505 elif cmd == 'password':
507 seed = wallet.pw_decode( wallet.seed, password)
509 sys.exit("Error: Password does not decrypt this wallet.")
511 new_password = prompt_password('New password:')
512 wallet.update_password(seed, password, new_password)
514 elif cmd == 'signmessage':
516 print_error("Error: Invalid usage of signmessage.")
517 print known_commands[cmd]
520 message = ' '.join(args[2:])
522 print "Warning: Message was reconstructed from several arguments:", repr(message)
523 print wallet.sign_message(address, message, password)
525 elif cmd == 'verifymessage':
529 message = ' '.join(args[3:])
531 print_error("Error: Not all parameters were given, displaying help instead.")
532 print known_commands[cmd]
535 print "Warning: Message was reconstructed from several arguments:", repr(message)
537 wallet.verify_message(address, signature, message)
539 except BaseException as e:
540 print "Verification error: {0}".format(e)
543 elif cmd == 'freeze':
545 print self.wallet.freeze(addr)
547 elif cmd == 'unfreeze':
549 print self.wallet.unfreeze(addr)
551 elif cmd == 'prioritize':
553 print self.wallet.prioritize(addr)
555 elif cmd == 'unprioritize':
557 print self.wallet.unprioritize(addr)