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/>.
26 sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
31 sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
36 from electrum import *
38 from decimal import Decimal
41 'help':'Prints this help',
42 'validateaddress':'Check that the address is valid',
43 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
44 'contacts': "Show your list of contacts",
45 'create':'Create a wallet',
46 'restore':'Restore a wallet',
47 'payto':"""Create and broadcast a transaction.
48 Syntax: payto <recipient> <amount> [label]
49 <recipient> can be a bitcoin address or a label
50 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
53 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
55 "Changes your password",
57 """Shows your list of addresses.
59 -a: show all addresses, including change addresses
61 -b: show the balance of addresses""",
63 'history':"Shows the transaction history",
64 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
66 """Create a signed transaction, password protected.
67 Syntax: mktx <recipient> <amount> [label]
68 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
71 "Print the generation seed of your wallet.",
73 'Imports a key pair\nSyntax: import <address>:<privatekey>',
75 'Signs a message with a key\nSyntax: signmessage <address> <message>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello This is a weird String "',
77 'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello This is a weird String "',
79 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
81 "Get config parameter.",
83 "Set config parameter.",
85 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
87 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
96 offline_commands = [ 'password', 'mktx',
98 'help', 'validateaddress',
99 'signmessage', 'verifymessage',
100 'eval', 'set', 'get', 'create', 'addresses',
104 'prioritize','unprioritize']
107 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
109 # get password routine
110 def prompt_password(prompt, confirm=True):
112 if sys.stdin.isatty():
113 password = getpass.getpass(prompt)
114 if password and confirm:
115 password2 = getpass.getpass("Confirm: ")
116 if password != password2:
117 sys.exit("Error: Passwords do not match.")
119 password = raw_input(prompt)
125 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
126 parser = optparse.OptionParser(prog=usage)
127 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
128 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
129 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
130 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
131 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
132 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
133 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
134 parser.add_option("-F", "--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.")
135 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")
136 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
137 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
138 parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
139 return parser.parse_args()
142 if __name__ == '__main__':
144 options, args = parse_args()
145 set_verbosity(options.verbose)
147 # config is an object passed to the various constructors (wallet, interface, gui)
148 if 'ANDROID_DATA' in os.environ:
149 config_options = {'wallet_path':"/sdcard/electrum.dat", 'blockchain_headers_path':'/sdcard', 'gui':'android'}
151 config_options = eval(str(options))
152 for k, v in config_options.items():
153 if v is None: config_options.pop(k)
155 config = SimpleConfig(config_options)
156 wallet = Wallet(config)
161 elif len(args)==1 and re.match('^bitcoin:', args[0]):
166 firstarg = args[1] if len(args) > 1 else ''
168 #this entire if/else block is just concerned with importing the
169 #right GUI toolkit based the GUI command line option given
171 pref_gui = config.get('gui','classic')
172 if pref_gui == 'gtk':
174 import lib.gui as gui
176 import electrum.gui as gui
177 elif pref_gui in ['classic', 'qt']:
179 import lib.gui_qt as gui
181 import electrum.gui_qt as gui
182 elif pref_gui == 'lite':
184 import lib.gui_lite as gui
186 import electrum.gui_lite as gui
187 elif pref_gui == 'text':
189 import lib.gui_text as gui
191 import electrum.gui_text as gui
192 elif pref_gui == 'android':
194 import lib.gui_android as gui
196 import electrum.gui_android as gui
198 sys.exit("Error: Unknown GUI: " + pref_gui )
200 interface = Interface(config, True)
202 interface.send([('server.peers.subscribe',[])])
204 wallet.interface = interface
206 gui = gui.ElectrumGui(wallet, config)
207 interface.register_callback('peers', gui.server_list_changed)
209 verifier = WalletVerifier(interface, config)
210 wallet.set_verifier(verifier)
212 WalletSynchronizer(wallet, config).start()
214 found = config.wallet_file_exists
216 found = gui.restore_or_create()
217 except SystemExit(e):
219 except BaseException(e):
221 traceback.print_exc(file=sys.stdout)
222 #gui.show_message(e.message)
234 if cmd not in known_commands:
237 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
238 print("Error: Wallet file not found.")
239 print("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
242 if cmd in ['create', 'restore']:
243 if config.wallet_file_exists:
244 sys.exit("Error: Remove the existing wallet first!")
245 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
247 server = config.get('server')
248 if not server: server = pick_random_server()
249 w_host, w_port, w_protocol = server.split(':')
250 host = raw_input("server (default:%s):"%w_host)
251 port = raw_input("port (default:%s):"%w_port)
252 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
253 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
254 gap = raw_input("gap limit (default 5):")
255 if host: w_host = host
256 if port: w_port = port
257 if protocol: w_protocol = protocol
258 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
259 if fee: wallet.fee = float(fee)
260 if gap: wallet.gap_limit = int(gap)
263 seed = raw_input("seed:")
267 print_error("Warning: Not hex, trying decode.")
268 seed = mnemonic_decode( seed.split(' ') )
270 sys.exit("Error: No seed")
272 wallet.seed = str(seed)
273 wallet.init_mpk( wallet.seed )
274 if not options.offline:
276 interface = Interface(config)
278 wallet.interface = interface
280 verifier = WalletVerifier(interface, config)
281 wallet.set_verifier(verifier)
283 print("Recovering wallet...")
284 WalletSynchronizer(wallet, config).start()
285 wallet.up_to_date_event.clear()
286 wallet.up_to_date = False
288 if wallet.is_found():
289 print("Recovery successful")
291 print("Warning: Found no history for this wallet")
294 wallet.fill_addressbook()
296 print("Wallet saved in '%s'"%wallet.config.path)
298 wallet.new_seed(None)
299 wallet.init_mpk( wallet.seed )
300 wallet.synchronize() # there is no wallet thread
302 print("Your wallet generation seed is: " + wallet.seed)
303 print("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
304 print("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
305 print("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
306 print("Wallet saved in '%s'"%wallet.config.path)
309 wallet.update_password(wallet.seed, None, password)
312 if cmd in ['payto', 'mktx']:
315 amount = int( 100000000 * Decimal(args[2]) )
317 label = ' '.join(args[3:])
319 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
325 if cmd not in offline_commands and not options.offline:
326 interface = Interface(config)
327 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
329 wallet.interface = interface
330 WalletSynchronizer(wallet, config).start()
334 # check if --from_addr not in wallet (for mktx/payto)
337 if options.from_addr:
338 from_addr = options.from_addr
339 if from_addr not in wallet.all_addresses():
343 if cmd=='addresses' and options.show_keys:
344 print("WARNING: ALL your private keys are secret.")
345 print("Exposing a single private key can compromise your entire wallet!")
346 print("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
348 # commands needing password
349 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
350 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
353 wallet.pw_decode( wallet.seed, password)
355 print_error("Error: This password does not decode this wallet.")
359 # See if they specificed a key on the cmd line, if not prompt
363 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
365 wallet.import_key(keypair,password)
367 print("Keypair imported")
368 except BaseException(e):
369 print_error("Error: Keypair import failed: " + str(e))
373 if cmd2 not in known_commands:
375 print("Type 'electrum help <command>' to see the help for a specific command")
376 print("Type 'electrum --help' to see the list of options")
377 print("List of commands:", ', '.join(known_commands))
379 print(known_commands[cmd2])
382 seed = wallet.pw_decode( wallet.seed, password)
383 print(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
385 elif cmd == 'deseed':
387 print_error("Error: This wallet has no seed")
388 elif wallet.use_encryption:
389 print_error("Error: This wallet is encrypted")
391 ns = wallet.path + '.seed'
392 print("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns))
393 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
395 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
398 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
402 print_error("Action canceled.")
404 elif cmd == 'reseed':
406 print("Warning: This wallet already has a seed", wallet.seed)
408 ns = wallet.path + '.seed'
414 sys.exit("Error: Seed file not found")
417 d = ast.literal_eval( data )
419 imported_keys = d.get('imported_keys',{})
421 sys.exit("Error: Error with seed file")
423 mpk = wallet.master_public_key
425 wallet.imported_keys = imported_keys
426 wallet.use_encryption = False
427 wallet.init_mpk(seed)
428 if mpk == wallet.master_public_key:
430 print("Done: " + wallet.path)
432 print_error("Error: Master public key does not match")
434 elif cmd == 'validateaddress':
436 print(wallet.is_valid(addr))
438 elif cmd == 'balance':
444 c, u = wallet.get_balance()
446 print(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
448 print(Decimal( c ) / 100000000)
451 c, u = wallet.get_addr_balance(addr)
453 print("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
455 print("%s %s" % (addr, str(Decimal(c)/100000000)))
457 elif cmd in [ 'contacts']:
458 for addr in wallet.addressbook:
459 print(addr, " ", wallet.labels.get(addr))
467 print(wallet.config.get(key))
470 key, value = args[1:3]
471 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
472 wallet.config.set_key(key, value, True)
477 elif cmd in [ 'addresses']:
478 for addr in wallet.all_addresses():
479 if options.show_all or not wallet.is_change(addr):
481 flags = wallet.get_address_flags(addr)
482 label = wallet.labels.get(addr,'')
484 if label: label = "\"%s\""%label
486 if options.show_balance:
487 h = wallet.history.get(addr,[])
490 # if item['is_input']: ni += 1
492 b = format_satoshis(wallet.get_addr_balance(addr)[0])
495 if options.show_keys:
496 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
497 print(flags, m_addr, b, label)
501 for item in wallet.get_tx_history():
502 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
504 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
508 label, is_default_label = wallet.get_label(tx_hash)
509 if not label: label = tx_hash
510 else: label = label + ' '*(64 - len(label) )
512 print("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
513 print("# balance: ", format_satoshis(balance))
518 label = ' '.join(args[2:])
520 print_error("Error. Syntax: label <tx_hash> <text>")
522 wallet.labels[tx] = label
525 elif cmd in ['payto', 'mktx']:
526 if from_addr and is_temporary:
527 if from_addr.find(":") == -1:
528 keypair = from_addr + ":" + prompt_password('Private key:', False)
531 from_addr = keypair.split(':')[0]
532 if not wallet.import_key(keypair,password):
533 print_error("Error: Invalid key pair")
535 wallet.history[from_addr] = interface.retrieve_history(from_addr)
536 wallet.update_tx_history()
537 change_addr = from_addr
539 if options.change_addr:
540 change_addr = options.change_addr
542 for k, v in wallet.labels.items():
545 print("alias", to_address)
547 if change_addr and v == change_addr:
550 tx = wallet.mktx( to_address, amount, label, password,
551 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
554 traceback.print_exc(file=sys.stdout)
557 if tx and cmd=='payto':
558 r, h = wallet.sendtx( tx )
564 wallet.imported_keys.pop(from_addr)
565 del(wallet.history[from_addr])
568 elif cmd == 'sendtx':
570 r, h = wallet.sendtx( tx )
573 elif cmd == 'password':
575 seed = wallet.pw_decode( wallet.seed, password)
577 sys.exit("Error: Password does not decrypt this wallet.")
579 new_password = prompt_password('New password:')
580 wallet.update_password(seed, password, new_password)
582 elif cmd == 'signmessage':
584 print_error("Error: Invalid usage of signmessage.")
585 print(known_commands[cmd])
588 message = ' '.join(args[2:])
590 print("Warning: Message was reconstructed from several arguments:", repr(message))
591 print(wallet.sign_message(address, message, password))
593 elif cmd == 'verifymessage':
597 message = ' '.join(args[3:])
599 print_error("Error: Not all parameters were given, displaying help instead.")
600 print(known_commands[cmd])
603 print("Warning: Message was reconstructed from several arguments:", repr(message))
605 wallet.verify_message(address, signature, message)
607 except BaseException as e:
608 print_error("Verification error: {0}".format(e))
611 elif cmd == 'freeze':
613 print(wallet.freeze(addr))
615 elif cmd == 'unfreeze':
617 print(wallet.unfreeze(addr))
619 elif cmd == 'prioritize':
621 print(wallet.prioritize(addr))
623 elif cmd == 'unprioritize':
625 print(wallet.unprioritize(addr))