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, Interface, WalletSynchronizer, WalletVerifier, format_satoshis, mnemonic, SimpleConfig, pick_random_server
41 from electrum import Wallet, Interface, WalletSynchronizer, WalletVerifier, format_satoshis, mnemonic, SimpleConfig, pick_random_server
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>\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 "',
82 '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 "',
84 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
86 "Set wallet parameter. (gui)",
88 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
90 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
99 offline_commands = [ 'password', 'mktx',
101 'help', 'validateaddress',
102 'signmessage', 'verifymessage',
103 'eval', 'set', 'create', 'addresses',
107 'prioritize','unprioritize']
110 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
112 # get password routine
113 def prompt_password(prompt, confirm=True):
115 if sys.stdin.isatty():
116 password = getpass.getpass(prompt)
117 if password and confirm:
118 password2 = getpass.getpass("Confirm: ")
119 if password != password2:
120 sys.exit("Error: Passwords do not match.")
122 password = raw_input(prompt)
129 if __name__ == '__main__':
131 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
132 parser = optparse.OptionParser(prog=usage)
133 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
134 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
135 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
136 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
137 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
138 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
139 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
140 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.")
141 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")
142 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
143 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
144 options, args = parser.parse_args()
146 # config is an object passed to the various constructors (wallet, interface, gui)
147 config = SimpleConfig(options)
148 wallet = Wallet(config)
153 elif len(args)==1 and re.match('^bitcoin:', args[0]):
158 firstarg = args[1] if len(args) > 1 else ''
160 #this entire if/else block is just concerned with importing the
161 #right GUI toolkit based the GUI command line option given
163 pref_gui = config.get('gui','classic')
164 if pref_gui == 'gtk':
166 import lib.gui as gui
168 import electrum.gui as gui
169 elif pref_gui in ['classic', 'qt']:
171 import lib.gui_qt as gui
173 import electrum.gui_qt as gui
174 elif pref_gui == 'lite':
176 import lib.gui_lite as gui
178 import electrum.gui_lite as gui
179 elif pref_gui == 'text':
181 import lib.gui_text as gui
183 import electrum.gui_text as gui
185 sys.exit("Error: Unknown GUI: " + pref_gui )
187 gui = gui.ElectrumGui(wallet, config)
188 interface = Interface(config, True)
189 interface.register_callback('peers', gui.server_list_changed)
191 wallet.interface = interface
193 WalletSynchronizer(wallet, config).start()
194 WalletVerifier(wallet, config).start()
197 found = config.wallet_file_exists
199 found = gui.restore_or_create()
200 except SystemExit, e:
202 except BaseException, e:
204 traceback.print_exc(file=sys.stdout)
205 #gui.show_message(e.message)
214 if cmd not in known_commands:
217 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
218 print "Error: Wallet file not found."
219 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
222 if cmd in ['create', 'restore']:
223 if config.wallet_file_exists:
224 sys.exit("Error: Remove the existing wallet first!")
225 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
227 server = config.get('server')
228 if not server: server = pick_random_server()
229 w_host, w_port, w_protocol = server.split(':')
230 host = raw_input("server (default:%s):"%w_host)
231 port = raw_input("port (default:%s):"%w_port)
232 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
233 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
234 gap = raw_input("gap limit (default 5):")
235 if host: w_host = host
236 if port: w_port = port
237 if protocol: w_protocol = protocol
238 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
239 if fee: wallet.fee = float(fee)
240 if gap: wallet.gap_limit = int(gap)
243 seed = raw_input("seed:")
247 print_error("Warning: Not hex, trying decode.")
248 seed = mnemonic.mn_decode( seed.split(' ') )
250 sys.exit("Error: No seed")
252 wallet.seed = str(seed)
253 wallet.init_mpk( wallet.seed )
254 if not options.offline:
255 WalletSynchronizer(wallet, config).start()
256 print "Recovering wallet..."
257 wallet.up_to_date_event.clear()
258 wallet.up_to_date = False
260 if wallet.is_found():
261 print "Recovery successful"
263 print_error("Warning: Found no history for this wallet")
266 wallet.fill_addressbook()
268 print_error("Wallet saved in '" + wallet.path)
270 wallet.new_seed(None)
271 wallet.init_mpk( wallet.seed )
272 wallet.synchronize() # there is no wallet thread
274 print "Your wallet generation seed is: " + wallet.seed
275 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
276 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
277 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
278 print "Wallet saved in '%s'"%wallet.config.path
281 wallet.update_password(wallet.seed, None, password)
284 if cmd in ['payto', 'mktx']:
287 amount = int( 100000000 * Decimal(args[2]) )
289 label = ' '.join(args[3:])
291 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
297 if cmd not in offline_commands and not options.offline:
298 interface = Interface(config)
299 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
301 wallet.interface = interface
302 WalletSynchronizer(wallet, config).start()
306 # check if --from_addr not in wallet (for mktx/payto)
309 if options.from_addr:
310 from_addr = options.from_addr
311 if from_addr not in wallet.all_addresses():
315 if cmd=='addresses' and options.show_keys:
316 print "WARNING: ALL your private keys are secret."
317 print "Exposing a single private key can compromise your entire wallet!"
318 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
320 # commands needing password
321 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
322 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
325 wallet.pw_decode( wallet.seed, password)
327 print_error("Error: This password does not decode this wallet.")
331 # See if they specificed a key on the cmd line, if not prompt
335 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
337 wallet.import_key(keypair,password)
339 print "Keypair imported"
340 except BaseException, e:
341 print_error("Error: Keypair import failed: " + str(e))
345 if cmd2 not in known_commands:
347 print "Type 'electrum help <command>' to see the help for a specific command"
348 print "Type 'electrum --help' to see the list of options"
349 print "List of commands:", ', '.join(known_commands)
351 print known_commands[cmd2]
354 seed = wallet.pw_decode( wallet.seed, password)
355 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
357 elif cmd == 'deseed':
359 print_error("Error: This wallet has no seed")
360 elif wallet.use_encryption:
361 print_error("Error: This wallet is encrypted")
363 ns = wallet.path + '.seed'
364 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
365 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
367 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
370 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
374 print_error("Action canceled.")
376 elif cmd == 'reseed':
378 print "Warning: This wallet already has a seed", wallet.seed
380 ns = wallet.path + '.seed'
386 sys.exit("Error: Seed file not found")
389 d = ast.literal_eval( data )
391 imported_keys = d.get('imported_keys',{})
393 sys.exit("Error: Error with seed file")
395 mpk = wallet.master_public_key
397 wallet.imported_keys = imported_keys
398 wallet.use_encryption = False
399 wallet.init_mpk(seed)
400 if mpk == wallet.master_public_key:
402 print "Done: " + wallet.path
404 print_error("Error: Master public key does not match")
406 elif cmd == 'validateaddress':
408 print wallet.is_valid(addr)
410 elif cmd == 'balance':
416 c, u = wallet.get_balance()
418 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
420 print Decimal( c ) / 100000000
423 c, u = wallet.get_addr_balance(addr)
425 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
427 print "%s %s" % (addr, str(Decimal(c)/100000000))
429 elif cmd in [ 'contacts']:
430 for addr in wallet.addressbook:
431 print addr, " ", wallet.labels.get(addr)
438 key, value = args[1:3]
439 if key in ['gui', 'server', 'proxy', 'fee', 'gap_limit', 'use_change']:
440 wallet.config.set_key(key, value, True)
445 elif cmd in [ 'addresses']:
446 for addr in wallet.all_addresses():
447 if options.show_all or not wallet.is_change(addr):
449 flags = wallet.get_address_flags(addr)
450 label = wallet.labels.get(addr,'')
452 if label: label = "\"%s\""%label
454 if options.show_balance:
455 h = wallet.history.get(addr,[])
458 # if item['is_input']: ni += 1
460 b = format_satoshis(wallet.get_addr_balance(addr)[0])
463 if options.show_keys:
464 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
465 print flags, m_addr, b, label
468 lines = wallet.get_tx_history()
475 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
477 print line['timestamp']
479 label = line.get('label')
480 if not label: label = line['tx_hash']
481 else: label = label + ' '*(64 - len(label) )
483 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
484 print "# balance: ", format_satoshis(b)
489 label = ' '.join(args[2:])
491 print_error("Error. Syntax: label <tx_hash> <text>")
493 wallet.labels[tx] = label
496 elif cmd in ['payto', 'mktx']:
497 if from_addr and is_temporary:
498 if from_addr.find(":") == -1:
499 keypair = from_addr + ":" + prompt_password('Private key:', False)
502 from_addr = keypair.split(':')[0]
503 if not wallet.import_key(keypair,password):
504 print_error("Error: Invalid key pair")
506 wallet.history[from_addr] = interface.retrieve_history(from_addr)
507 wallet.update_tx_history()
508 change_addr = from_addr
510 if options.change_addr:
511 change_addr = options.change_addr
513 for k, v in wallet.labels.items():
516 print "alias", to_address
518 if change_addr and v == change_addr:
521 tx = wallet.mktx( to_address, amount, label, password,
522 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
525 traceback.print_exc(file=sys.stdout)
528 if tx and cmd=='payto':
529 r, h = wallet.sendtx( tx )
535 wallet.imported_keys.pop(from_addr)
536 del(wallet.history[from_addr])
539 elif cmd == 'sendtx':
541 r, h = wallet.sendtx( tx )
544 elif cmd == 'password':
546 seed = wallet.pw_decode( wallet.seed, password)
548 sys.exit("Error: Password does not decrypt this wallet.")
550 new_password = prompt_password('New password:')
551 wallet.update_password(seed, password, new_password)
553 elif cmd == 'signmessage':
555 print_error("Error: Invalid usage of signmessage.")
556 print known_commands[cmd]
559 message = ' '.join(args[2:])
561 print "Warning: Message was reconstructed from several arguments:", repr(message)
562 print wallet.sign_message(address, message, password)
564 elif cmd == 'verifymessage':
568 message = ' '.join(args[3:])
570 print_error("Error: Not all parameters were given, displaying help instead.")
571 print known_commands[cmd]
574 print "Warning: Message was reconstructed from several arguments:", repr(message)
576 wallet.verify_message(address, signature, message)
578 except BaseException as e:
579 print "Verification error: {0}".format(e)
582 elif cmd == 'freeze':
584 print wallet.freeze(addr)
586 elif cmd == 'unfreeze':
588 print wallet.unfreeze(addr)
590 elif cmd == 'prioritize':
592 print wallet.prioritize(addr)
594 elif cmd == 'unprioritize':
596 print wallet.unprioritize(addr)