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/>.
21 from lib.util import print_error
23 from electrum.util import print_error
28 print_error("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
34 print_error("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
38 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
40 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
42 from optparse import OptionParser
43 from decimal import Decimal
46 'help':'Prints this help',
47 'validateaddress':'Check that the address is valid',
48 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
49 'contacts': "Show your list of contacts",
50 'create':'Create a wallet',
51 'restore':'Restore a wallet',
52 'payto':"""Create and broadcast a transaction.
53 Syntax: payto <recipient> <amount> [label]
54 <recipient> can be a bitcoin address or a label
55 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
58 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
60 "Changes your password",
62 """Shows your list of addresses.
64 -a: show all addresses, including change addresses
66 -b: show the balance of addresses""",
68 'history':"Shows the transaction history",
69 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
71 """Create a signed transaction, password protected.
72 Syntax: mktx <recipient> <amount> [label]
73 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
76 "Print the generation seed of your wallet.",
78 'Imports a key pair\nSyntax: import <address>:<privatekey>',
80 'Signs a message with a key\nSyntax: signmessage <address> <message>',
82 'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>',
84 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
86 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
88 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
97 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
99 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
101 if __name__ == '__main__':
103 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
104 parser = OptionParser(usage=usage)
105 parser.add_option("-g", "--gui", dest="gui", default="lite", help="gui")
106 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
107 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
108 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
109 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
110 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
111 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
112 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.")
113 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")
114 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
115 options, args = parser.parse_args()
118 wallet.set_path(options.wallet_path)
120 wallet.remote_url = options.remote_url
125 elif len(args)==1 and re.match('^bitcoin:', args[0]):
130 firstarg = args[1] if len(args) > 1 else ''
134 if options.gui=='gtk':
136 import lib.gui as gui
138 import electrum.gui as gui
139 elif options.gui=='qt':
141 import lib.gui_qt as gui
143 import electrum.gui_qt as gui
144 elif options.gui == 'lite':
145 # Let's do some dep checking and handle missing ones gracefully
147 from PyQt4.QtCore import *
148 from PyQt4.QtGui import *
150 print "You need to have PyQT installed to run Electrum in graphical mode."
151 print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
154 qtVersion = qVersion()
155 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
156 app = QApplication(sys.argv)
158 error_message = QErrorMessage()
159 error_message.setFixedSize(350,200)
160 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>")
165 import lib.gui_lite as gui
167 import electrum.gui_lite as gui
169 print_error("Error: Unknown GUI: " + options.gui)
172 gui = gui.ElectrumGui(wallet)
173 WalletSynchronizer(wallet,True).start()
176 found = wallet.file_exists
178 found = gui.restore_or_create()
179 except SystemExit, e:
181 except BaseException, e:
183 traceback.print_exc(file=sys.stdout)
184 #gui.show_message(e.message)
193 if cmd not in known_commands:
196 if not wallet.file_exists and cmd not in ['help','create','restore']:
197 print_error("Error: Wallet file not found.")
198 print_error("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
201 if cmd in ['create', 'restore']:
202 if wallet.file_exists:
203 print_error("Error: Remove the existing wallet first!")
206 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
208 w_host, w_port, w_protocol = wallet.server.split(':')
209 host = raw_input("server (default:%s):"%w_host)
210 port = raw_input("port (default:%s):"%w_port)
211 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
212 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
213 gap = raw_input("gap limit (default 5):")
214 if host: w_host = host
215 if port: w_port = port
216 if protocol: w_protocol = protocol
217 wallet.server = w_host + ':' + w_port + ':' +w_protocol
218 if fee: wallet.fee = float(fee)
219 if gap: wallet.gap_limit = int(gap)
222 seed = raw_input("seed:")
226 print_error("Warning: Not hex, trying decode.")
227 seed = mnemonic.mn_decode( seed.split(' ') )
229 print_error("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:
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 print_error("Error: Seed file not found")
359 d = ast.literal_eval( data )
361 imported_keys = d.get('imported_keys',{})
363 print_error("Error: Error with seed file")
366 mpk = wallet.master_public_key
368 wallet.imported_keys = imported_keys
369 wallet.use_encryption = False
370 wallet.init_mpk(seed)
371 if mpk == wallet.master_public_key:
373 print "Done: " + wallet.path
375 print_error("Error: Master public key does not match")
377 elif cmd == 'validateaddress':
379 print wallet.is_valid(addr)
381 elif cmd == 'balance':
387 c, u = wallet.get_balance()
389 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
391 print Decimal( c ) / 100000000
394 c, u = wallet.get_addr_balance(addr)
396 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
398 print "%s %s" % (addr, str(Decimal(c)/100000000))
400 elif cmd in [ 'contacts']:
401 for addr in wallet.addressbook:
402 print addr, " ", wallet.labels.get(addr)
408 elif cmd in [ 'addresses']:
409 for addr in wallet.all_addresses():
410 if options.show_all or not wallet.is_change(addr):
412 flags = wallet.get_address_flags(addr)
413 label = wallet.labels.get(addr,'')
415 if label: label = "\"%s\""%label
417 if options.show_balance:
418 h = wallet.history.get(addr,[])
421 # if item['is_input']: ni += 1
423 b = format_satoshis(wallet.get_addr_balance(addr)[0])
426 if options.show_keys:
427 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
428 print flags, m_addr, b, label
431 lines = wallet.get_tx_history()
438 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
440 print line['timestamp']
442 label = line.get('label')
443 if not label: label = line['tx_hash']
444 else: label = label + ' '*(64 - len(label) )
446 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
447 print "# balance: ", format_satoshis(b)
452 label = ' '.join(args[2:])
454 print_error("Error. Syntax: label <tx_hash> <text>")
456 wallet.labels[tx] = label
459 elif cmd in ['payto', 'mktx']:
460 if from_addr and is_temporary:
461 if from_addr.find(":") == -1:
462 keypair = from_addr + ":" + prompt_password('Private key:', False)
465 from_addr = keypair.split(':')[0]
466 if not wallet.import_key(keypair,password):
467 print_error("Error: Invalid key pair")
469 wallet.history[from_addr] = interface.retrieve_history(from_addr)
470 wallet.update_tx_history()
471 change_addr = from_addr
473 if options.change_addr:
474 change_addr = options.change_addr
476 for k, v in wallet.labels.items():
479 print "alias", to_address
481 if change_addr and v == change_addr:
484 tx = wallet.mktx( to_address, amount, label, password,
485 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
488 traceback.print_exc(file=sys.stdout)
491 if tx and cmd=='payto':
492 r, h = wallet.sendtx( tx )
498 wallet.imported_keys.pop(from_addr)
499 del(wallet.history[from_addr])
502 elif cmd == 'sendtx':
504 r, h = wallet.sendtx( tx )
507 elif cmd == 'password':
509 seed = wallet.pw_decode( wallet.seed, password)
511 print_error("Error: Password does not decrypt this wallet.")
514 new_password = prompt_password('New password:')
515 wallet.update_password(seed, password, new_password)
517 elif cmd == 'signmessage':
519 print_error("Error: Invalid usage of signmessage.")
520 print known_commands[cmd]
523 message = ' '.join(args[2:])
525 print "Warning: Message was reconstructed from several arguments:", repr(message)
526 print wallet.sign_message(address, message, password)
528 elif cmd == 'verifymessage':
532 message = ' '.join(args[3:])
534 print_error("Error: Not all parameters were given, displaying help instead.")
535 print known_commands[cmd]
538 print "Warning: Message was reconstructed from several arguments:", repr(message)
540 wallet.verify_message(address, signature, message)
545 elif cmd == 'freeze':
547 print self.wallet.freeze(addr)
549 elif cmd == 'unfreeze':
551 print self.wallet.unfreeze(addr)
553 elif cmd == 'prioritize':
555 print self.wallet.prioritize(addr)
557 elif cmd == 'unprioritize':
559 print self.wallet.unprioritize(addr)