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/>.
19 import re, sys, getpass
24 print "python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'"
30 print "AES does not seem to be installed. Try 'sudo pip install slowaes'"
34 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic
36 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic
38 from optparse import OptionParser
39 from decimal import Decimal
42 'help':'Prints this help',
43 'validateaddress':'Check that the address is valid',
44 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
45 'contacts': "Show your list of contacts",
46 'create':'Create a wallet',
47 'restore':'Restore a wallet',
48 'payto':"""Create and broadcast a transaction.
49 Syntax: payto <recipient> <amount> [label]
50 <recipient> can be a bitcoin address or a label
51 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
54 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
56 "Changes your password",
58 """Shows your list of addresses.
60 -a: show all addresses, including change addresses
62 -b: show the balance of addresses""",
64 'history':"Shows the transaction history",
65 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
67 """Create a signed transaction, password protected.
68 Syntax: mktx <recipient> <amount> [label]
69 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
72 "Print the generation seed of your wallet.",
74 'Imports a key pair\nSyntax: import <address>:<privatekey>',
76 'Signs a message with a key\nSyntax: signmessage <address> <message>',
78 'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>',
80 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
82 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
84 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
93 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
95 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
97 if __name__ == '__main__':
99 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
100 parser = OptionParser(usage=usage)
101 parser.add_option("-g", "--gui", dest="gui", default="lite", help="gui")
102 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
103 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
104 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
105 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
106 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
107 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
108 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.")
109 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")
110 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
111 options, args = parser.parse_args()
114 wallet.set_path(options.wallet_path)
116 wallet.remote_url = options.remote_url
121 elif len(args)==1 and re.match('^bitcoin:', args[0]):
126 firstarg = args[1] if len(args) > 1 else ''
129 if options.gui=='gtk':
131 import lib.gui as gui
133 import electrum.gui as gui
134 elif options.gui=='qt':
136 import lib.gui_qt as gui
138 import electrum.gui_qt as gui
139 elif options.gui == 'lite':
141 import lib.gui_lite as gui
143 import electrum.gui_lite as gui
145 print "unknown gui", options.gui
148 gui = gui.ElectrumGui(wallet)
149 WalletSynchronizer(wallet,True).start()
152 found = wallet.file_exists
154 found = gui.restore_or_create()
155 except SystemExit, e:
157 except BaseException, e:
159 traceback.print_exc(file=sys.stdout)
160 #gui.show_message(e.message)
169 if cmd not in known_commands:
172 if not wallet.file_exists and cmd not in ['help','create','restore']:
173 print "Wallet file not found."
174 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
177 if cmd in ['create', 'restore']:
178 if wallet.file_exists:
179 print "remove the existing wallet first!"
181 password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):")
183 password2 = getpass.getpass("Confirm password:")
184 if password != password2:
190 w_host, w_port, w_protocol = wallet.server.split(':')
191 host = raw_input("server (default:%s):"%w_host)
192 port = raw_input("port (default:%s):"%w_port)
193 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
194 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
195 gap = raw_input("gap limit (default 5):")
196 if host: w_host = host
197 if port: w_port = port
198 if protocol: w_protocol = protocol
199 wallet.server = w_host + ':' + w_port + ':' +w_protocol
200 if fee: wallet.fee = float(fee)
201 if gap: wallet.gap_limit = int(gap)
204 seed = raw_input("seed:")
208 print "not hex, trying decode"
209 seed = mnemonic.mn_decode( seed.split(' ') )
214 wallet.seed = str(seed)
215 wallet.init_mpk( wallet.seed )
216 if not options.offline:
217 WalletSynchronizer(wallet).start()
218 print "recovering wallet..."
219 wallet.up_to_date_event.clear()
220 wallet.up_to_date = False
222 if wallet.is_found():
223 print "recovery successful"
225 print "found no history for this wallet"
226 wallet.fill_addressbook()
228 print "Wallet saved in '%s'"%wallet.path
230 wallet.new_seed(None)
231 wallet.init_mpk( wallet.seed )
232 wallet.synchronize() # there is no wallet thread
234 print "Your wallet generation seed is: " + wallet.seed
235 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
236 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
237 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
238 print "Wallet saved in '%s'"%wallet.path
241 wallet.update_password(wallet.seed, None, password)
244 if cmd in ['payto', 'mktx']:
247 amount = int( 100000000 * Decimal(args[2]) )
249 label = ' '.join(args[3:])
251 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
257 if cmd not in offline_commands and not options.offline:
258 WalletSynchronizer(wallet).start()
262 # check if --from_addr not in wallet (for mktx/payto)
265 if options.from_addr:
266 from_addr = options.from_addr
267 if from_addr not in wallet.all_addresses():
270 # commands needing password
271 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
272 password = getpass.getpass('Password:') if wallet.use_encryption and not is_temporary else None
275 wallet.pw_decode( wallet.seed, password)
277 print "invalid password"
283 wallet.import_key(keypair,password)
285 print "keypair imported"
286 except BaseException, e:
287 print( 'Error:' + str(e) )
291 if cmd2 not in known_commands:
292 print "type 'electrum help <command>' to see the help for a specific command"
293 print "type 'electrum --help' to see the list of options"
294 print "list of commands:", ', '.join(known_commands)
296 print known_commands[cmd2]
299 seed = wallet.pw_decode( wallet.seed, password)
300 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
302 elif cmd == 'deseed':
304 print "Error: This wallet has no seed"
305 elif wallet.use_encryption:
306 print "Error: This wallet is encrypted"
308 ns = wallet.path + '.seed'
309 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
310 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
312 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
315 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
319 print "Action canceled."
321 elif cmd == 'reseed':
323 print "This wallet already has a seed", wallet.seed
325 ns = wallet.path + '.seed'
331 print "seed file not found"
335 d = ast.literal_eval( data )
337 imported_keys = d.get('imported_keys',{})
339 print "error with seed file"
342 mpk = wallet.master_public_key
344 wallet.imported_keys = imported_keys
345 wallet.use_encryption = False
346 wallet.init_mpk(seed)
347 if mpk == wallet.master_public_key:
349 print "Done: " + wallet.path
351 print "error: master public key does not match"
353 elif cmd == 'validateaddress':
355 print wallet.is_valid(addr)
357 elif cmd == 'balance':
363 c, u = wallet.get_balance()
365 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
367 print Decimal( c ) / 100000000
370 c, u = wallet.get_addr_balance(addr)
372 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
374 print "%s %s" % (addr, str(Decimal(c)/100000000))
376 elif cmd in [ 'contacts']:
377 for addr in wallet.addressbook:
378 print addr, " ", wallet.labels.get(addr)
384 elif cmd in [ 'addresses']:
385 for addr in wallet.all_addresses():
386 if options.show_all or not wallet.is_change(addr):
388 flags = wallet.get_address_flags(addr)
389 label = wallet.labels.get(addr,'')
391 if label: label = "\"%s\""%label
393 if options.show_balance:
394 h = wallet.history.get(addr,[])
397 # if item['is_input']: ni += 1
399 b = format_satoshis(wallet.get_addr_balance(addr)[0])
402 if options.show_keys:
403 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
404 print flags, m_addr, b, label
407 lines = wallet.get_tx_history()
414 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
416 print line['timestamp']
418 label = line.get('label')
419 if not label: label = line['tx_hash']
420 else: label = label + ' '*(64 - len(label) )
422 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
423 print "# balance: ", format_satoshis(b)
428 label = ' '.join(args[2:])
430 print "syntax: label <tx_hash> <text>"
432 wallet.labels[tx] = label
435 elif cmd in ['payto', 'mktx']:
436 if from_addr and is_temporary:
437 if from_addr.find(":") == -1:
438 keypair = from_addr + ":" + getpass.getpass('Private key:')
441 from_addr = keypair.split(':')[0]
442 if not wallet.import_key(keypair,password):
443 print "invalid key pair"
445 wallet.history[from_addr] = interface.retrieve_history(from_addr)
446 wallet.update_tx_history()
447 change_addr = from_addr
449 if options.change_addr:
450 change_addr = options.change_addr
452 for k, v in wallet.labels.items():
455 print "alias", to_address
457 if change_addr and v == change_addr:
460 tx = wallet.mktx( to_address, amount, label, password,
461 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
464 traceback.print_exc(file=sys.stdout)
467 if tx and cmd=='payto':
468 r, h = wallet.sendtx( tx )
474 wallet.imported_keys.pop(from_addr)
475 del(wallet.history[from_addr])
478 elif cmd == 'sendtx':
480 r, h = wallet.sendtx( tx )
483 elif cmd == 'password':
485 seed = wallet.pw_decode( wallet.seed, password)
489 new_password = getpass.getpass('New password:')
490 if new_password == getpass.getpass('Confirm new password:'):
491 wallet.update_password(seed, password, new_password)
493 print "error: mismatch"
495 elif cmd == 'signmessage':
497 message = ' '.join(args[2:])
499 print "Warning: Message was reconstructed from several arguments:", repr(message)
500 print wallet.sign_message(address, message, password)
502 elif cmd == 'verifymessage':
506 message = ' '.join(args[3:])
508 print "Not all parameters were given, displaying help instead."
509 print known_commands[cmd]
512 print "Warning: Message was reconstructed from several arguments:", repr(message)
514 wallet.verify_message(address, signature, message)
519 elif cmd == 'freeze':
521 print self.wallet.freeze(addr)
523 elif cmd == 'unfreeze':
525 print self.wallet.unfreeze(addr)
527 elif cmd == 'prioritize':
529 print self.wallet.prioritize(addr)
531 elif cmd == 'unprioritize':
533 print self.wallet.unprioritize(addr)