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 WalletSynchronizer(wallet,True).start()
179 found = wallet.file_exists
181 found = gui.restore_or_create()
182 except SystemExit, e:
184 except BaseException, e:
186 traceback.print_exc(file=sys.stdout)
187 #gui.show_message(e.message)
196 if cmd not in known_commands:
199 if not wallet.file_exists and cmd not in ['help','create','restore']:
200 print "Error: Wallet file not found."
201 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
204 if cmd in ['create', 'restore']:
205 if wallet.file_exists:
206 sys.exit("Error: Remove the existing wallet first!")
207 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
209 w_host, w_port, w_protocol = wallet.server.split(':')
210 host = raw_input("server (default:%s):"%w_host)
211 port = raw_input("port (default:%s):"%w_port)
212 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
213 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
214 gap = raw_input("gap limit (default 5):")
215 if host: w_host = host
216 if port: w_port = port
217 if protocol: w_protocol = protocol
218 wallet.server = w_host + ':' + w_port + ':' +w_protocol
219 if fee: wallet.fee = float(fee)
220 if gap: wallet.gap_limit = int(gap)
223 seed = raw_input("seed:")
227 print_error("Warning: Not hex, trying decode.")
228 seed = mnemonic.mn_decode( seed.split(' ') )
230 sys.exit("Error: No seed")
232 wallet.seed = str(seed)
233 wallet.init_mpk( wallet.seed )
234 if not options.offline:
235 WalletSynchronizer(wallet).start()
236 print "Recovering wallet..."
237 wallet.up_to_date_event.clear()
238 wallet.up_to_date = False
240 if wallet.is_found():
241 print "Recovery successful"
243 print_error("Warning: Found no history for this wallet")
244 wallet.fill_addressbook()
246 print_error("Wallet saved in '" + wallet.path)
248 wallet.new_seed(None)
249 wallet.init_mpk( wallet.seed )
250 wallet.synchronize() # there is no wallet thread
252 print "Your wallet generation seed is: " + wallet.seed
253 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
254 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
255 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
256 print "Wallet saved in '%s'"%wallet.path
259 wallet.update_password(wallet.seed, None, password)
262 if cmd in ['payto', 'mktx']:
265 amount = int( 100000000 * Decimal(args[2]) )
267 label = ' '.join(args[3:])
269 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
275 if cmd not in offline_commands and not options.offline:
276 WalletSynchronizer(wallet).start()
280 # check if --from_addr not in wallet (for mktx/payto)
283 if options.from_addr:
284 from_addr = options.from_addr
285 if from_addr not in wallet.all_addresses():
288 # commands needing password
289 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
290 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
293 wallet.pw_decode( wallet.seed, password)
295 print_error("Error: This password does not decode this wallet.")
299 # See if they specificed a key on the cmd line, if not prompt
303 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
305 wallet.import_key(keypair,password)
307 print "Keypair imported"
308 except BaseException, e:
309 print_error("Error: Keypair import failed: " + str(e))
313 if cmd2 not in known_commands:
315 print "Type 'electrum help <command>' to see the help for a specific command"
316 print "Type 'electrum --help' to see the list of options"
317 print "List of commands:", ', '.join(known_commands)
319 print known_commands[cmd2]
322 seed = wallet.pw_decode( wallet.seed, password)
323 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
325 elif cmd == 'deseed':
327 print_error("Error: This wallet has no seed")
328 elif wallet.use_encryption:
329 print_error("Error: This wallet is encrypted")
331 ns = wallet.path + '.seed'
332 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
333 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
335 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
338 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
342 print_error("Action canceled.")
344 elif cmd == 'reseed':
346 print "Warning: This wallet already has a seed", wallet.seed
348 ns = wallet.path + '.seed'
354 sys.exit("Error: Seed file not found")
357 d = ast.literal_eval( data )
359 imported_keys = d.get('imported_keys',{})
361 sys.exit("Error: Error with seed file")
363 mpk = wallet.master_public_key
365 wallet.imported_keys = imported_keys
366 wallet.use_encryption = False
367 wallet.init_mpk(seed)
368 if mpk == wallet.master_public_key:
370 print "Done: " + wallet.path
372 print_error("Error: Master public key does not match")
374 elif cmd == 'validateaddress':
376 print wallet.is_valid(addr)
378 elif cmd == 'balance':
384 c, u = wallet.get_balance()
386 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
388 print Decimal( c ) / 100000000
391 c, u = wallet.get_addr_balance(addr)
393 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
395 print "%s %s" % (addr, str(Decimal(c)/100000000))
397 elif cmd in [ 'contacts']:
398 for addr in wallet.addressbook:
399 print addr, " ", wallet.labels.get(addr)
405 elif cmd in [ 'addresses']:
406 for addr in wallet.all_addresses():
407 if options.show_all or not wallet.is_change(addr):
409 flags = wallet.get_address_flags(addr)
410 label = wallet.labels.get(addr,'')
412 if label: label = "\"%s\""%label
414 if options.show_balance:
415 h = wallet.history.get(addr,[])
418 # if item['is_input']: ni += 1
420 b = format_satoshis(wallet.get_addr_balance(addr)[0])
423 if options.show_keys:
424 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
425 print flags, m_addr, b, label
428 lines = wallet.get_tx_history()
435 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
437 print line['timestamp']
439 label = line.get('label')
440 if not label: label = line['tx_hash']
441 else: label = label + ' '*(64 - len(label) )
443 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
444 print "# balance: ", format_satoshis(b)
449 label = ' '.join(args[2:])
451 print_error("Error. Syntax: label <tx_hash> <text>")
453 wallet.labels[tx] = label
456 elif cmd in ['payto', 'mktx']:
457 if from_addr and is_temporary:
458 if from_addr.find(":") == -1:
459 keypair = from_addr + ":" + prompt_password('Private key:', False)
462 from_addr = keypair.split(':')[0]
463 if not wallet.import_key(keypair,password):
464 print_error("Error: Invalid key pair")
466 wallet.history[from_addr] = interface.retrieve_history(from_addr)
467 wallet.update_tx_history()
468 change_addr = from_addr
470 if options.change_addr:
471 change_addr = options.change_addr
473 for k, v in wallet.labels.items():
476 print "alias", to_address
478 if change_addr and v == change_addr:
481 tx = wallet.mktx( to_address, amount, label, password,
482 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
485 traceback.print_exc(file=sys.stdout)
488 if tx and cmd=='payto':
489 r, h = wallet.sendtx( tx )
495 wallet.imported_keys.pop(from_addr)
496 del(wallet.history[from_addr])
499 elif cmd == 'sendtx':
501 r, h = wallet.sendtx( tx )
504 elif cmd == 'password':
506 seed = wallet.pw_decode( wallet.seed, password)
507 except StandardError:
508 sys.exit("Error: Password does not decrypt this wallet.")
510 new_password = prompt_password('New password:')
511 wallet.update_password(seed, password, new_password)
513 elif cmd == 'signmessage':
515 print_error("Error: Invalid usage of signmessage.")
516 print known_commands[cmd]
519 message = ' '.join(args[2:])
521 print "Warning: Message was reconstructed from several arguments:", repr(message)
522 print wallet.sign_message(address, message, password)
524 elif cmd == 'verifymessage':
528 message = ' '.join(args[3:])
530 print_error("Error: Not all parameters were given, displaying help instead.")
531 print known_commands[cmd]
534 print "Warning: Message was reconstructed from several arguments:", repr(message)
536 wallet.verify_message(address, signature, message)
538 except BaseException as e:
539 print "Verification error: {0}".format(e)
542 elif cmd == 'freeze':
544 print self.wallet.freeze(addr)
546 elif cmd == 'unfreeze':
548 print self.wallet.unfreeze(addr)
550 elif cmd == 'prioritize':
552 print self.wallet.prioritize(addr)
554 elif cmd == 'unprioritize':
556 print self.wallet.unprioritize(addr)