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 wallet.interface = Interface(config, True, gui.server_list_changed)
189 wallet.interface.start()
191 WalletSynchronizer(wallet, config).start()
192 WalletVerifier(wallet, config).start()
195 found = config.wallet_file_exists
197 found = gui.restore_or_create()
198 except SystemExit, e:
200 except BaseException, e:
202 traceback.print_exc(file=sys.stdout)
203 #gui.show_message(e.message)
212 if cmd not in known_commands:
215 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
216 print "Error: Wallet file not found."
217 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
220 if cmd in ['create', 'restore']:
221 if config.wallet_file_exists:
222 sys.exit("Error: Remove the existing wallet first!")
223 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
225 server = config.get('server')
226 if not server: server = pick_random_server()
227 w_host, w_port, w_protocol = server.split(':')
228 host = raw_input("server (default:%s):"%w_host)
229 port = raw_input("port (default:%s):"%w_port)
230 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
231 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
232 gap = raw_input("gap limit (default 5):")
233 if host: w_host = host
234 if port: w_port = port
235 if protocol: w_protocol = protocol
236 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
237 if fee: wallet.fee = float(fee)
238 if gap: wallet.gap_limit = int(gap)
241 seed = raw_input("seed:")
245 print_error("Warning: Not hex, trying decode.")
246 seed = mnemonic.mn_decode( seed.split(' ') )
248 sys.exit("Error: No seed")
250 wallet.seed = str(seed)
251 wallet.init_mpk( wallet.seed )
252 if not options.offline:
253 WalletSynchronizer(wallet, config).start()
254 print "Recovering wallet..."
255 wallet.up_to_date_event.clear()
256 wallet.up_to_date = False
258 if wallet.is_found():
259 print "Recovery successful"
261 print_error("Warning: Found no history for this wallet")
264 wallet.fill_addressbook()
266 print_error("Wallet saved in '" + wallet.path)
268 wallet.new_seed(None)
269 wallet.init_mpk( wallet.seed )
270 wallet.synchronize() # there is no wallet thread
272 print "Your wallet generation seed is: " + wallet.seed
273 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
274 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
275 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
276 print "Wallet saved in '%s'"%wallet.config.path
279 wallet.update_password(wallet.seed, None, password)
282 if cmd in ['payto', 'mktx']:
285 amount = int( 100000000 * Decimal(args[2]) )
287 label = ' '.join(args[3:])
289 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
295 if cmd not in offline_commands and not options.offline:
296 wallet.interface = Interface(config)
297 wallet.interface.start()
298 WalletSynchronizer(wallet, config).start()
302 # check if --from_addr not in wallet (for mktx/payto)
305 if options.from_addr:
306 from_addr = options.from_addr
307 if from_addr not in wallet.all_addresses():
311 if cmd=='addresses' and options.show_keys:
312 print "WARNING: ALL your private keys are secret."
313 print "Exposing a single private key can compromise your entire wallet!"
314 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
316 # commands needing password
317 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
318 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
321 wallet.pw_decode( wallet.seed, password)
323 print_error("Error: This password does not decode this wallet.")
327 # See if they specificed a key on the cmd line, if not prompt
331 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
333 wallet.import_key(keypair,password)
335 print "Keypair imported"
336 except BaseException, e:
337 print_error("Error: Keypair import failed: " + str(e))
341 if cmd2 not in known_commands:
343 print "Type 'electrum help <command>' to see the help for a specific command"
344 print "Type 'electrum --help' to see the list of options"
345 print "List of commands:", ', '.join(known_commands)
347 print known_commands[cmd2]
350 seed = wallet.pw_decode( wallet.seed, password)
351 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
353 elif cmd == 'deseed':
355 print_error("Error: This wallet has no seed")
356 elif wallet.use_encryption:
357 print_error("Error: This wallet is encrypted")
359 ns = wallet.path + '.seed'
360 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
361 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
363 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
366 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
370 print_error("Action canceled.")
372 elif cmd == 'reseed':
374 print "Warning: This wallet already has a seed", wallet.seed
376 ns = wallet.path + '.seed'
382 sys.exit("Error: Seed file not found")
385 d = ast.literal_eval( data )
387 imported_keys = d.get('imported_keys',{})
389 sys.exit("Error: Error with seed file")
391 mpk = wallet.master_public_key
393 wallet.imported_keys = imported_keys
394 wallet.use_encryption = False
395 wallet.init_mpk(seed)
396 if mpk == wallet.master_public_key:
398 print "Done: " + wallet.path
400 print_error("Error: Master public key does not match")
402 elif cmd == 'validateaddress':
404 print wallet.is_valid(addr)
406 elif cmd == 'balance':
412 c, u = wallet.get_balance()
414 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
416 print Decimal( c ) / 100000000
419 c, u = wallet.get_addr_balance(addr)
421 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
423 print "%s %s" % (addr, str(Decimal(c)/100000000))
425 elif cmd in [ 'contacts']:
426 for addr in wallet.addressbook:
427 print addr, " ", wallet.labels.get(addr)
434 key, value = args[1:3]
435 if key in ['gui', 'server', 'proxy', 'fee', 'gap_limit', 'use_change']:
436 wallet.config.set_key(key, value, True)
441 elif cmd in [ 'addresses']:
442 for addr in wallet.all_addresses():
443 if options.show_all or not wallet.is_change(addr):
445 flags = wallet.get_address_flags(addr)
446 label = wallet.labels.get(addr,'')
448 if label: label = "\"%s\""%label
450 if options.show_balance:
451 h = wallet.history.get(addr,[])
454 # if item['is_input']: ni += 1
456 b = format_satoshis(wallet.get_addr_balance(addr)[0])
459 if options.show_keys:
460 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
461 print flags, m_addr, b, label
464 lines = wallet.get_tx_history()
471 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
473 print line['timestamp']
475 label = line.get('label')
476 if not label: label = line['tx_hash']
477 else: label = label + ' '*(64 - len(label) )
479 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
480 print "# balance: ", format_satoshis(b)
485 label = ' '.join(args[2:])
487 print_error("Error. Syntax: label <tx_hash> <text>")
489 wallet.labels[tx] = label
492 elif cmd in ['payto', 'mktx']:
493 if from_addr and is_temporary:
494 if from_addr.find(":") == -1:
495 keypair = from_addr + ":" + prompt_password('Private key:', False)
498 from_addr = keypair.split(':')[0]
499 if not wallet.import_key(keypair,password):
500 print_error("Error: Invalid key pair")
502 wallet.history[from_addr] = interface.retrieve_history(from_addr)
503 wallet.update_tx_history()
504 change_addr = from_addr
506 if options.change_addr:
507 change_addr = options.change_addr
509 for k, v in wallet.labels.items():
512 print "alias", to_address
514 if change_addr and v == change_addr:
517 tx = wallet.mktx( to_address, amount, label, password,
518 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
521 traceback.print_exc(file=sys.stdout)
524 if tx and cmd=='payto':
525 r, h = wallet.sendtx( tx )
531 wallet.imported_keys.pop(from_addr)
532 del(wallet.history[from_addr])
535 elif cmd == 'sendtx':
537 r, h = wallet.sendtx( tx )
540 elif cmd == 'password':
542 seed = wallet.pw_decode( wallet.seed, password)
544 sys.exit("Error: Password does not decrypt this wallet.")
546 new_password = prompt_password('New password:')
547 wallet.update_password(seed, password, new_password)
549 elif cmd == 'signmessage':
551 print_error("Error: Invalid usage of signmessage.")
552 print known_commands[cmd]
555 message = ' '.join(args[2:])
557 print "Warning: Message was reconstructed from several arguments:", repr(message)
558 print wallet.sign_message(address, message, password)
560 elif cmd == 'verifymessage':
564 message = ' '.join(args[3:])
566 print_error("Error: Not all parameters were given, displaying help instead.")
567 print known_commands[cmd]
570 print "Warning: Message was reconstructed from several arguments:", repr(message)
572 wallet.verify_message(address, signature, message)
574 except BaseException as e:
575 print "Verification error: {0}".format(e)
578 elif cmd == 'freeze':
580 print wallet.freeze(addr)
582 elif cmd == 'unfreeze':
584 print wallet.unfreeze(addr)
586 elif cmd == 'prioritize':
588 print wallet.prioritize(addr)
590 elif cmd == 'unprioritize':
592 print wallet.unprioritize(addr)