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!")
203 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
205 w_host, w_port, w_protocol = wallet.server.split(':')
206 host = raw_input("server (default:%s):"%w_host)
207 port = raw_input("port (default:%s):"%w_port)
208 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
209 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
210 gap = raw_input("gap limit (default 5):")
211 if host: w_host = host
212 if port: w_port = port
213 if protocol: w_protocol = protocol
214 wallet.server = w_host + ':' + w_port + ':' +w_protocol
215 if fee: wallet.fee = float(fee)
216 if gap: wallet.gap_limit = int(gap)
219 seed = raw_input("seed:")
223 print_error("Warning: Not hex, trying decode.")
224 seed = mnemonic.mn_decode( seed.split(' ') )
226 sys.exit("Error: No seed")
228 wallet.seed = str(seed)
229 wallet.init_mpk( wallet.seed )
230 if not options.offline:
231 WalletSynchronizer(wallet).start()
232 print "Recovering wallet..."
233 wallet.up_to_date_event.clear()
234 wallet.up_to_date = False
236 if wallet.is_found():
237 print "Recovery successful"
239 print_error("Warning: Found no history for this wallet")
240 wallet.fill_addressbook()
242 print_error("Wallet saved in '" + wallet.path)
244 wallet.new_seed(None)
245 wallet.init_mpk( wallet.seed )
246 wallet.synchronize() # there is no wallet thread
248 print "Your wallet generation seed is: " + wallet.seed
249 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
250 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
251 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
252 print "Wallet saved in '%s'"%wallet.path
255 wallet.update_password(wallet.seed, None, password)
258 if cmd in ['payto', 'mktx']:
261 amount = int( 100000000 * Decimal(args[2]) )
263 label = ' '.join(args[3:])
265 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
271 if cmd not in offline_commands and not options.offline:
272 WalletSynchronizer(wallet).start()
276 # check if --from_addr not in wallet (for mktx/payto)
279 if options.from_addr:
280 from_addr = options.from_addr
281 if from_addr not in wallet.all_addresses():
284 # commands needing password
285 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
286 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
289 wallet.pw_decode( wallet.seed, password)
291 print_error("Error: This password does not decode this wallet.")
295 # See if they specificed a key on the cmd line, if not prompt
299 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
301 wallet.import_key(keypair,password)
303 print "Keypair imported"
304 except BaseException, e:
305 print_error("Error: Keypair import failed: " + str(e))
309 if cmd2 not in known_commands:
312 print "Type 'electrum help <command>' to see the help for a specific command"
313 print "Type 'electrum --help' to see the list of options"
314 print "List of commands:", ', '.join(known_commands)
316 print known_commands[cmd2]
319 seed = wallet.pw_decode( wallet.seed, password)
320 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
322 elif cmd == 'deseed':
324 print_error("Error: This wallet has no seed")
325 elif wallet.use_encryption:
326 print_error("Error: This wallet is encrypted")
328 ns = wallet.path + '.seed'
329 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
330 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
332 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
335 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
339 print_error("Action canceled.")
341 elif cmd == 'reseed':
343 print "Warning: This wallet already has a seed", wallet.seed
345 ns = wallet.path + '.seed'
351 sys.exit("Error: Seed file not found")
354 d = ast.literal_eval( data )
356 imported_keys = d.get('imported_keys',{})
358 sys.exit("Error: Error with seed file")
360 mpk = wallet.master_public_key
362 wallet.imported_keys = imported_keys
363 wallet.use_encryption = False
364 wallet.init_mpk(seed)
365 if mpk == wallet.master_public_key:
367 print "Done: " + wallet.path
369 print_error("Error: Master public key does not match")
371 elif cmd == 'validateaddress':
373 print wallet.is_valid(addr)
375 elif cmd == 'balance':
381 c, u = wallet.get_balance()
383 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
385 print Decimal( c ) / 100000000
388 c, u = wallet.get_addr_balance(addr)
390 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
392 print "%s %s" % (addr, str(Decimal(c)/100000000))
394 elif cmd in [ 'contacts']:
395 for addr in wallet.addressbook:
396 print addr, " ", wallet.labels.get(addr)
402 elif cmd in [ 'addresses']:
403 for addr in wallet.all_addresses():
404 if options.show_all or not wallet.is_change(addr):
406 flags = wallet.get_address_flags(addr)
407 label = wallet.labels.get(addr,'')
409 if label: label = "\"%s\""%label
411 if options.show_balance:
412 h = wallet.history.get(addr,[])
415 # if item['is_input']: ni += 1
417 b = format_satoshis(wallet.get_addr_balance(addr)[0])
420 if options.show_keys:
421 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
422 print flags, m_addr, b, label
425 lines = wallet.get_tx_history()
432 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
434 print line['timestamp']
436 label = line.get('label')
437 if not label: label = line['tx_hash']
438 else: label = label + ' '*(64 - len(label) )
440 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
441 print "# balance: ", format_satoshis(b)
446 label = ' '.join(args[2:])
448 print_error("Error. Syntax: label <tx_hash> <text>")
450 wallet.labels[tx] = label
453 elif cmd in ['payto', 'mktx']:
454 if from_addr and is_temporary:
455 if from_addr.find(":") == -1:
456 keypair = from_addr + ":" + prompt_password('Private key:', False)
459 from_addr = keypair.split(':')[0]
460 if not wallet.import_key(keypair,password):
461 print_error("Error: Invalid key pair")
463 wallet.history[from_addr] = interface.retrieve_history(from_addr)
464 wallet.update_tx_history()
465 change_addr = from_addr
467 if options.change_addr:
468 change_addr = options.change_addr
470 for k, v in wallet.labels.items():
473 print "alias", to_address
475 if change_addr and v == change_addr:
478 tx = wallet.mktx( to_address, amount, label, password,
479 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
482 traceback.print_exc(file=sys.stdout)
485 if tx and cmd=='payto':
486 r, h = wallet.sendtx( tx )
492 wallet.imported_keys.pop(from_addr)
493 del(wallet.history[from_addr])
496 elif cmd == 'sendtx':
498 r, h = wallet.sendtx( tx )
501 elif cmd == 'password':
503 seed = wallet.pw_decode( wallet.seed, password)
504 except StandardError:
505 sys.exit("Error: Password does not decrypt this wallet.")
507 new_password = prompt_password('New password:')
508 wallet.update_password(seed, password, new_password)
510 elif cmd == 'signmessage':
512 print_error("Error: Invalid usage of signmessage.")
513 print known_commands[cmd]
516 message = ' '.join(args[2:])
518 print "Warning: Message was reconstructed from several arguments:", repr(message)
519 print wallet.sign_message(address, message, password)
521 elif cmd == 'verifymessage':
525 message = ' '.join(args[3:])
527 print_error("Error: Not all parameters were given, displaying help instead.")
528 print known_commands[cmd]
531 print "Warning: Message was reconstructed from several arguments:", repr(message)
533 wallet.verify_message(address, signature, message)
538 elif cmd == 'freeze':
540 print self.wallet.freeze(addr)
542 elif cmd == 'unfreeze':
544 print self.wallet.unfreeze(addr)
546 elif cmd == 'prioritize':
548 print self.wallet.prioritize(addr)
550 elif cmd == 'unprioritize':
552 print self.wallet.unprioritize(addr)