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 ''
132 if options.gui=='gtk':
134 import lib.gui as gui
136 import electrum.gui as gui
137 elif options.gui=='qt':
139 import lib.gui_qt as gui
141 import electrum.gui_qt as gui
142 elif options.gui == 'lite':
143 # Let's do some dep checking and handle missing ones gracefully
145 from PyQt4.QtCore import *
146 from PyQt4.QtGui import *
148 print "You need to have PyQT installed to run Electrum in graphical mode."
149 print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
152 qtVersion = qVersion()
153 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
154 app = QApplication(sys.argv)
156 error_message = QErrorMessage()
157 error_message.setFixedSize(350,200)
158 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>")
163 import lib.gui_lite as gui
165 import electrum.gui_lite as gui
167 print_error("Error: Unknown GUI: " + options.gui)
170 gui = gui.ElectrumGui(wallet)
171 WalletSynchronizer(wallet,True).start()
174 found = wallet.file_exists
176 found = gui.restore_or_create()
177 except SystemExit, e:
179 except BaseException, e:
181 traceback.print_exc(file=sys.stdout)
182 #gui.show_message(e.message)
191 if cmd not in known_commands:
194 if not wallet.file_exists and cmd not in ['help','create','restore']:
195 print "Error: Wallet file not found."
196 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
199 if cmd in ['create', 'restore']:
200 if wallet.file_exists:
201 sys.exit("Error: Remove the existing wallet first!")
202 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
204 w_host, w_port, w_protocol = wallet.server.split(':')
205 host = raw_input("server (default:%s):"%w_host)
206 port = raw_input("port (default:%s):"%w_port)
207 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
208 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
209 gap = raw_input("gap limit (default 5):")
210 if host: w_host = host
211 if port: w_port = port
212 if protocol: w_protocol = protocol
213 wallet.server = w_host + ':' + w_port + ':' +w_protocol
214 if fee: wallet.fee = float(fee)
215 if gap: wallet.gap_limit = int(gap)
218 seed = raw_input("seed:")
222 print_error("Warning: Not hex, trying decode.")
223 seed = mnemonic.mn_decode( seed.split(' ') )
225 sys.exit("Error: No seed")
227 wallet.seed = str(seed)
228 wallet.init_mpk( wallet.seed )
229 if not options.offline:
230 WalletSynchronizer(wallet).start()
231 print "Recovering wallet..."
232 wallet.up_to_date_event.clear()
233 wallet.up_to_date = False
235 if wallet.is_found():
236 print "Recovery successful"
238 print_error("Warning: Found no history for this wallet")
239 wallet.fill_addressbook()
241 print_error("Wallet saved in '" + wallet.path)
243 wallet.new_seed(None)
244 wallet.init_mpk( wallet.seed )
245 wallet.synchronize() # there is no wallet thread
247 print "Your wallet generation seed is: " + wallet.seed
248 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
249 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
250 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
251 print "Wallet saved in '%s'"%wallet.path
254 wallet.update_password(wallet.seed, None, password)
257 if cmd in ['payto', 'mktx']:
260 amount = int( 100000000 * Decimal(args[2]) )
262 label = ' '.join(args[3:])
264 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
270 if cmd not in offline_commands and not options.offline:
271 WalletSynchronizer(wallet).start()
275 # check if --from_addr not in wallet (for mktx/payto)
278 if options.from_addr:
279 from_addr = options.from_addr
280 if from_addr not in wallet.all_addresses():
283 # commands needing password
284 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
285 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
288 wallet.pw_decode( wallet.seed, password)
290 print_error("Error: This password does not decode this wallet.")
294 # See if they specificed a key on the cmd line, if not prompt
298 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
300 wallet.import_key(keypair,password)
302 print "Keypair imported"
303 except BaseException, e:
304 print_error("Error: Keypair import failed: " + str(e))
308 if cmd2 not in known_commands:
311 print "Type 'electrum help <command>' to see the help for a specific command"
312 print "Type 'electrum --help' to see the list of options"
313 print "List of commands:", ', '.join(known_commands)
315 print known_commands[cmd2]
318 seed = wallet.pw_decode( wallet.seed, password)
319 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
321 elif cmd == 'deseed':
323 print_error("Error: This wallet has no seed")
324 elif wallet.use_encryption:
325 print_error("Error: This wallet is encrypted")
327 ns = wallet.path + '.seed'
328 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
329 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
331 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
334 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
338 print_error("Action canceled.")
340 elif cmd == 'reseed':
342 print "Warning: This wallet already has a seed", wallet.seed
344 ns = wallet.path + '.seed'
350 sys.exit("Error: Seed file not found")
353 d = ast.literal_eval( data )
355 imported_keys = d.get('imported_keys',{})
357 sys.exit("Error: Error with seed file")
359 mpk = wallet.master_public_key
361 wallet.imported_keys = imported_keys
362 wallet.use_encryption = False
363 wallet.init_mpk(seed)
364 if mpk == wallet.master_public_key:
366 print "Done: " + wallet.path
368 print_error("Error: Master public key does not match")
370 elif cmd == 'validateaddress':
372 print wallet.is_valid(addr)
374 elif cmd == 'balance':
380 c, u = wallet.get_balance()
382 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
384 print Decimal( c ) / 100000000
387 c, u = wallet.get_addr_balance(addr)
389 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
391 print "%s %s" % (addr, str(Decimal(c)/100000000))
393 elif cmd in [ 'contacts']:
394 for addr in wallet.addressbook:
395 print addr, " ", wallet.labels.get(addr)
401 elif cmd in [ 'addresses']:
402 for addr in wallet.all_addresses():
403 if options.show_all or not wallet.is_change(addr):
405 flags = wallet.get_address_flags(addr)
406 label = wallet.labels.get(addr,'')
408 if label: label = "\"%s\""%label
410 if options.show_balance:
411 h = wallet.history.get(addr,[])
414 # if item['is_input']: ni += 1
416 b = format_satoshis(wallet.get_addr_balance(addr)[0])
419 if options.show_keys:
420 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
421 print flags, m_addr, b, label
424 lines = wallet.get_tx_history()
431 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
433 print line['timestamp']
435 label = line.get('label')
436 if not label: label = line['tx_hash']
437 else: label = label + ' '*(64 - len(label) )
439 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
440 print "# balance: ", format_satoshis(b)
445 label = ' '.join(args[2:])
447 print_error("Error. Syntax: label <tx_hash> <text>")
449 wallet.labels[tx] = label
452 elif cmd in ['payto', 'mktx']:
453 if from_addr and is_temporary:
454 if from_addr.find(":") == -1:
455 keypair = from_addr + ":" + prompt_password('Private key:', False)
458 from_addr = keypair.split(':')[0]
459 if not wallet.import_key(keypair,password):
460 print_error("Error: Invalid key pair")
462 wallet.history[from_addr] = interface.retrieve_history(from_addr)
463 wallet.update_tx_history()
464 change_addr = from_addr
466 if options.change_addr:
467 change_addr = options.change_addr
469 for k, v in wallet.labels.items():
472 print "alias", to_address
474 if change_addr and v == change_addr:
477 tx = wallet.mktx( to_address, amount, label, password,
478 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
481 traceback.print_exc(file=sys.stdout)
484 if tx and cmd=='payto':
485 r, h = wallet.sendtx( tx )
491 wallet.imported_keys.pop(from_addr)
492 del(wallet.history[from_addr])
495 elif cmd == 'sendtx':
497 r, h = wallet.sendtx( tx )
500 elif cmd == 'password':
502 seed = wallet.pw_decode( wallet.seed, password)
503 except StandardError:
504 sys.exit("Error: Password does not decrypt this wallet.")
506 new_password = prompt_password('New password:')
507 wallet.update_password(seed, password, new_password)
509 elif cmd == 'signmessage':
511 print_error("Error: Invalid usage of signmessage.")
512 print known_commands[cmd]
515 message = ' '.join(args[2:])
517 print "Warning: Message was reconstructed from several arguments:", repr(message)
518 print wallet.sign_message(address, message, password)
520 elif cmd == 'verifymessage':
524 message = ' '.join(args[3:])
526 print_error("Error: Not all parameters were given, displaying help instead.")
527 print known_commands[cmd]
530 print "Warning: Message was reconstructed from several arguments:", repr(message)
532 wallet.verify_message(address, signature, message)
537 elif cmd == 'freeze':
539 print self.wallet.freeze(addr)
541 elif cmd == 'unfreeze':
543 print self.wallet.unfreeze(addr)
545 elif cmd == 'prioritize':
547 print self.wallet.prioritize(addr)
549 elif cmd == 'unprioritize':
551 print self.wallet.unprioritize(addr)