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/>.
24 from lib.util import print_error
26 from electrum.util import print_error
31 sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
36 sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
39 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
41 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
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 = argparse.ArgumentParser(prog=usage)
105 parser.add_argument("-g", "--gui", dest="gui", default="lite", help="gui")
106 parser.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
107 parser.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
108 parser.add_argument("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
109 parser.add_argument("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
110 parser.add_argument("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
111 parser.add_argument("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
112 parser.add_argument("-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_argument("-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_argument("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
115 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 ''
132 #this entire if/else block is just concerned with importing the
133 #right GUI toolkit based the GUI command line option given
136 if options.gui=='gtk':
138 import lib.gui as gui
140 import electrum.gui as gui
141 elif options.gui=='qt':
143 import lib.gui_qt as gui
145 import electrum.gui_qt as gui
146 elif options.gui == 'lite':
147 # Let's do some dep checking and handle missing ones gracefully
149 from PyQt4.QtCore import *
150 from PyQt4.QtGui import *
152 print "You need to have PyQT installed to run Electrum in graphical mode."
153 print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
156 qtVersion = qVersion()
157 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
158 app = QApplication(sys.argv)
160 error_message = QErrorMessage()
161 error_message.setFixedSize(350,200)
162 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>")
166 #use the lite version if no toolkit specified
168 import lib.gui_lite as gui
170 import electrum.gui_lite as gui
172 sys.exit("Error: Unknown GUI: " + options.gui)
174 gui = gui.ElectrumGui(wallet)
175 WalletSynchronizer(wallet,True).start()
178 found = wallet.file_exists
180 found = gui.restore_or_create()
182 exit("Error retrieving wallet file.")
183 except BaseException, e:
185 traceback.print_exc(file=sys.stdout)
186 #gui.show_message(e.message)
195 if cmd not in known_commands:
198 if not wallet.file_exists and cmd not in ['help','create','restore']:
199 print "Error: Wallet file not found."
200 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
203 if cmd in ['create', 'restore']:
204 if wallet.file_exists:
205 sys.exit("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 sys.exit("Error: No seed")
231 wallet.seed = str(seed)
232 wallet.init_mpk( wallet.seed )
233 if not options.offline:
234 WalletSynchronizer(wallet).start()
235 print "Recovering wallet..."
236 wallet.up_to_date_event.clear()
237 wallet.up_to_date = False
239 if wallet.is_found():
240 print "Recovery successful"
242 print_error("Warning: Found no history for this wallet")
243 wallet.fill_addressbook()
245 print_error("Wallet saved in '" + wallet.path)
247 wallet.new_seed(None)
248 wallet.init_mpk( wallet.seed )
249 wallet.synchronize() # there is no wallet thread
251 print "Your wallet generation seed is: " + wallet.seed
252 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
253 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
254 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
255 print "Wallet saved in '%s'"%wallet.path
258 wallet.update_password(wallet.seed, None, password)
261 if cmd in ['payto', 'mktx']:
264 amount = int( 100000000 * Decimal(args[2]) )
266 label = ' '.join(args[3:])
268 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
274 if cmd not in offline_commands and not options.offline:
275 WalletSynchronizer(wallet).start()
279 # check if --from_addr not in wallet (for mktx/payto)
282 if options.from_addr:
283 from_addr = options.from_addr
284 if from_addr not in wallet.all_addresses():
287 # commands needing password
288 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
289 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
292 wallet.pw_decode( wallet.seed, password)
294 print_error("Error: This password does not decode this wallet.")
298 # See if they specificed a key on the cmd line, if not prompt
302 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
304 wallet.import_key(keypair,password)
306 print "Keypair imported"
307 except BaseException, e:
308 print_error("Error: Keypair import failed: " + str(e))
312 if cmd2 not in known_commands:
314 print "Type 'electrum help <command>' to see the help for a specific command"
315 print "Type 'electrum --help' to see the list of options"
316 print "List of commands:", ', '.join(known_commands)
318 print known_commands[cmd2]
321 seed = wallet.pw_decode( wallet.seed, password)
322 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
324 elif cmd == 'deseed':
326 print_error("Error: This wallet has no seed")
327 elif wallet.use_encryption:
328 print_error("Error: This wallet is encrypted")
330 ns = wallet.path + '.seed'
331 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
332 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
334 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
337 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
341 print_error("Action canceled.")
343 elif cmd == 'reseed':
345 print "Warning: This wallet already has a seed", wallet.seed
347 ns = wallet.path + '.seed'
353 sys.exit("Error: Seed file not found")
356 d = ast.literal_eval( data )
358 imported_keys = d.get('imported_keys',{})
360 sys.exit("Error: Error with seed file")
362 mpk = wallet.master_public_key
364 wallet.imported_keys = imported_keys
365 wallet.use_encryption = False
366 wallet.init_mpk(seed)
367 if mpk == wallet.master_public_key:
369 print "Done: " + wallet.path
371 print_error("Error: Master public key does not match")
373 elif cmd == 'validateaddress':
375 print wallet.is_valid(addr)
377 elif cmd == 'balance':
383 c, u = wallet.get_balance()
385 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
387 print Decimal( c ) / 100000000
390 c, u = wallet.get_addr_balance(addr)
392 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
394 print "%s %s" % (addr, str(Decimal(c)/100000000))
396 elif cmd in [ 'contacts']:
397 for addr in wallet.addressbook:
398 print addr, " ", wallet.labels.get(addr)
404 elif cmd in [ 'addresses']:
405 for addr in wallet.all_addresses():
406 if options.show_all or not wallet.is_change(addr):
408 flags = wallet.get_address_flags(addr)
409 label = wallet.labels.get(addr,'')
411 if label: label = "\"%s\""%label
413 if options.show_balance:
414 h = wallet.history.get(addr,[])
417 # if item['is_input']: ni += 1
419 b = format_satoshis(wallet.get_addr_balance(addr)[0])
422 if options.show_keys:
423 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
424 print flags, m_addr, b, label
427 lines = wallet.get_tx_history()
434 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
436 print line['timestamp']
438 label = line.get('label')
439 if not label: label = line['tx_hash']
440 else: label = label + ' '*(64 - len(label) )
442 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
443 print "# balance: ", format_satoshis(b)
448 label = ' '.join(args[2:])
450 print_error("Error. Syntax: label <tx_hash> <text>")
452 wallet.labels[tx] = label
455 elif cmd in ['payto', 'mktx']:
456 if from_addr and is_temporary:
457 if from_addr.find(":") == -1:
458 keypair = from_addr + ":" + prompt_password('Private key:', False)
461 from_addr = keypair.split(':')[0]
462 if not wallet.import_key(keypair,password):
463 print_error("Error: Invalid key pair")
465 wallet.history[from_addr] = interface.retrieve_history(from_addr)
466 wallet.update_tx_history()
467 change_addr = from_addr
469 if options.change_addr:
470 change_addr = options.change_addr
472 for k, v in wallet.labels.items():
475 print "alias", to_address
477 if change_addr and v == change_addr:
480 tx = wallet.mktx( to_address, amount, label, password,
481 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
484 traceback.print_exc(file=sys.stdout)
487 if tx and cmd=='payto':
488 r, h = wallet.sendtx( tx )
494 wallet.imported_keys.pop(from_addr)
495 del(wallet.history[from_addr])
498 elif cmd == 'sendtx':
500 r, h = wallet.sendtx( tx )
503 elif cmd == 'password':
505 seed = wallet.pw_decode( wallet.seed, password)
506 except StandardError:
507 sys.exit("Error: Password does not decrypt this wallet.")
509 new_password = prompt_password('New password:')
510 wallet.update_password(seed, password, new_password)
512 elif cmd == 'signmessage':
514 print_error("Error: Invalid usage of signmessage.")
515 print known_commands[cmd]
518 message = ' '.join(args[2:])
520 print "Warning: Message was reconstructed from several arguments:", repr(message)
521 print wallet.sign_message(address, message, password)
523 elif cmd == 'verifymessage':
527 message = ' '.join(args[3:])
529 print_error("Error: Not all parameters were given, displaying help instead.")
530 print known_commands[cmd]
533 print "Warning: Message was reconstructed from several arguments:", repr(message)
535 wallet.verify_message(address, signature, message)
540 elif cmd == 'freeze':
542 print self.wallet.freeze(addr)
544 elif cmd == 'unfreeze':
546 print self.wallet.unfreeze(addr)
548 elif cmd == 'prioritize':
550 print self.wallet.prioritize(addr)
552 elif cmd == 'unprioritize':
554 print self.wallet.unprioritize(addr)