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 sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
33 sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
36 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
38 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
40 from optparse import OptionParser
41 from decimal import Decimal
44 'help':'Prints this help',
45 'validateaddress':'Check that the address is valid',
46 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
47 'contacts': "Show your list of contacts",
48 'create':'Create a wallet',
49 'restore':'Restore a wallet',
50 'payto':"""Create and broadcast a transaction.
51 Syntax: payto <recipient> <amount> [label]
52 <recipient> can be a bitcoin address or a label
53 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
56 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
58 "Changes your password",
60 """Shows your list of addresses.
62 -a: show all addresses, including change addresses
64 -b: show the balance of addresses""",
66 'history':"Shows the transaction history",
67 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
69 """Create a signed transaction, password protected.
70 Syntax: mktx <recipient> <amount> [label]
71 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
74 "Print the generation seed of your wallet.",
76 'Imports a key pair\nSyntax: import <address>:<privatekey>',
78 'Signs a message with a key\nSyntax: signmessage <address> <message>',
80 'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>',
82 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
84 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
86 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
95 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
97 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
99 if __name__ == '__main__':
101 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
102 parser = OptionParser(usage=usage)
103 parser.add_option("-g", "--gui", dest="gui", default="lite", help="gui")
104 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
105 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
106 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
107 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
108 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
109 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
110 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.")
111 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")
112 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
113 options, args = parser.parse_args()
116 wallet.set_path(options.wallet_path)
118 wallet.remote_url = options.remote_url
123 elif len(args)==1 and re.match('^bitcoin:', args[0]):
128 firstarg = args[1] if len(args) > 1 else ''
130 #this entire if/else block is just concerned with importing the
131 #right GUI toolkit based the GUI command line option given
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>")
164 #use the lite version if no toolkit specified
166 import lib.gui_lite as gui
168 import electrum.gui_lite as gui
170 sys.exit("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: Wallet file not found."
198 print "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 sys.exit("Error: Remove the existing wallet first!")
204 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
206 w_host, w_port, w_protocol = wallet.server.split(':')
207 host = raw_input("server (default:%s):"%w_host)
208 port = raw_input("port (default:%s):"%w_port)
209 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
210 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
211 gap = raw_input("gap limit (default 5):")
212 if host: w_host = host
213 if port: w_port = port
214 if protocol: w_protocol = protocol
215 wallet.server = w_host + ':' + w_port + ':' +w_protocol
216 if fee: wallet.fee = float(fee)
217 if gap: wallet.gap_limit = int(gap)
220 seed = raw_input("seed:")
224 print_error("Warning: Not hex, trying decode.")
225 seed = mnemonic.mn_decode( seed.split(' ') )
227 sys.exit("Error: No seed")
229 wallet.seed = str(seed)
230 wallet.init_mpk( wallet.seed )
231 if not options.offline:
232 WalletSynchronizer(wallet).start()
233 print "Recovering wallet..."
234 wallet.up_to_date_event.clear()
235 wallet.up_to_date = False
237 if wallet.is_found():
238 print "Recovery successful"
240 print_error("Warning: Found no history for this wallet")
241 wallet.fill_addressbook()
243 print_error("Wallet saved in '" + wallet.path)
245 wallet.new_seed(None)
246 wallet.init_mpk( wallet.seed )
247 wallet.synchronize() # there is no wallet thread
249 print "Your wallet generation seed is: " + wallet.seed
250 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
251 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
252 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
253 print "Wallet saved in '%s'"%wallet.path
256 wallet.update_password(wallet.seed, None, password)
259 if cmd in ['payto', 'mktx']:
262 amount = int( 100000000 * Decimal(args[2]) )
264 label = ' '.join(args[3:])
266 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
272 if cmd not in offline_commands and not options.offline:
273 WalletSynchronizer(wallet).start()
277 # check if --from_addr not in wallet (for mktx/payto)
280 if options.from_addr:
281 from_addr = options.from_addr
282 if from_addr not in wallet.all_addresses():
285 # commands needing password
286 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
287 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
290 wallet.pw_decode( wallet.seed, password)
292 print_error("Error: This password does not decode this wallet.")
296 # See if they specificed a key on the cmd line, if not prompt
300 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
302 wallet.import_key(keypair,password)
304 print "Keypair imported"
305 except BaseException, e:
306 print_error("Error: Keypair import failed: " + str(e))
310 if cmd2 not in known_commands:
313 print "Type 'electrum help <command>' to see the help for a specific command"
314 print "Type 'electrum --help' to see the list of options"
315 print "List of commands:", ', '.join(known_commands)
317 print known_commands[cmd2]
320 seed = wallet.pw_decode( wallet.seed, password)
321 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
323 elif cmd == 'deseed':
325 print_error("Error: This wallet has no seed")
326 elif wallet.use_encryption:
327 print_error("Error: This wallet is encrypted")
329 ns = wallet.path + '.seed'
330 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
331 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
333 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
336 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
340 print_error("Action canceled.")
342 elif cmd == 'reseed':
344 print "Warning: This wallet already has a seed", wallet.seed
346 ns = wallet.path + '.seed'
352 sys.exit("Error: Seed file not found")
355 d = ast.literal_eval( data )
357 imported_keys = d.get('imported_keys',{})
359 sys.exit("Error: Error with seed file")
361 mpk = wallet.master_public_key
363 wallet.imported_keys = imported_keys
364 wallet.use_encryption = False
365 wallet.init_mpk(seed)
366 if mpk == wallet.master_public_key:
368 print "Done: " + wallet.path
370 print_error("Error: Master public key does not match")
372 elif cmd == 'validateaddress':
374 print wallet.is_valid(addr)
376 elif cmd == 'balance':
382 c, u = wallet.get_balance()
384 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
386 print Decimal( c ) / 100000000
389 c, u = wallet.get_addr_balance(addr)
391 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
393 print "%s %s" % (addr, str(Decimal(c)/100000000))
395 elif cmd in [ 'contacts']:
396 for addr in wallet.addressbook:
397 print addr, " ", wallet.labels.get(addr)
403 elif cmd in [ 'addresses']:
404 for addr in wallet.all_addresses():
405 if options.show_all or not wallet.is_change(addr):
407 flags = wallet.get_address_flags(addr)
408 label = wallet.labels.get(addr,'')
410 if label: label = "\"%s\""%label
412 if options.show_balance:
413 h = wallet.history.get(addr,[])
416 # if item['is_input']: ni += 1
418 b = format_satoshis(wallet.get_addr_balance(addr)[0])
421 if options.show_keys:
422 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
423 print flags, m_addr, b, label
426 lines = wallet.get_tx_history()
433 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
435 print line['timestamp']
437 label = line.get('label')
438 if not label: label = line['tx_hash']
439 else: label = label + ' '*(64 - len(label) )
441 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
442 print "# balance: ", format_satoshis(b)
447 label = ' '.join(args[2:])
449 print_error("Error. Syntax: label <tx_hash> <text>")
451 wallet.labels[tx] = label
454 elif cmd in ['payto', 'mktx']:
455 if from_addr and is_temporary:
456 if from_addr.find(":") == -1:
457 keypair = from_addr + ":" + prompt_password('Private key:', False)
460 from_addr = keypair.split(':')[0]
461 if not wallet.import_key(keypair,password):
462 print_error("Error: Invalid key pair")
464 wallet.history[from_addr] = interface.retrieve_history(from_addr)
465 wallet.update_tx_history()
466 change_addr = from_addr
468 if options.change_addr:
469 change_addr = options.change_addr
471 for k, v in wallet.labels.items():
474 print "alias", to_address
476 if change_addr and v == change_addr:
479 tx = wallet.mktx( to_address, amount, label, password,
480 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
483 traceback.print_exc(file=sys.stdout)
486 if tx and cmd=='payto':
487 r, h = wallet.sendtx( tx )
493 wallet.imported_keys.pop(from_addr)
494 del(wallet.history[from_addr])
497 elif cmd == 'sendtx':
499 r, h = wallet.sendtx( tx )
502 elif cmd == 'password':
504 seed = wallet.pw_decode( wallet.seed, password)
505 except StandardError:
506 sys.exit("Error: Password does not decrypt this wallet.")
508 new_password = prompt_password('New password:')
509 wallet.update_password(seed, password, new_password)
511 elif cmd == 'signmessage':
513 print_error("Error: Invalid usage of signmessage.")
514 print known_commands[cmd]
517 message = ' '.join(args[2:])
519 print "Warning: Message was reconstructed from several arguments:", repr(message)
520 print wallet.sign_message(address, message, password)
522 elif cmd == 'verifymessage':
526 message = ' '.join(args[3:])
528 print_error("Error: Not all parameters were given, displaying help instead.")
529 print known_commands[cmd]
532 print "Warning: Message was reconstructed from several arguments:", repr(message)
534 wallet.verify_message(address, signature, message)
539 elif cmd == 'freeze':
541 print self.wallet.freeze(addr)
543 elif cmd == 'unfreeze':
545 print self.wallet.unfreeze(addr)
547 elif cmd == 'prioritize':
549 print self.wallet.prioritize(addr)
551 elif cmd == 'unprioritize':
553 print self.wallet.unprioritize(addr)