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/>.
25 from lib.util import print_error
27 from electrum.util import print_error
32 sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
37 sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
40 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, parse_proxy_options, SimpleConfig
42 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, parse_proxy_options, SimpleConfig
44 from decimal import Decimal
47 'help':'Prints this help',
48 'validateaddress':'Check that the address is valid',
49 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
50 'contacts': "Show your list of contacts",
51 'create':'Create a wallet',
52 'restore':'Restore a wallet',
53 'payto':"""Create and broadcast a transaction.
54 Syntax: payto <recipient> <amount> [label]
55 <recipient> can be a bitcoin address or a label
56 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
59 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
61 "Changes your password",
63 """Shows your list of addresses.
65 -a: show all addresses, including change addresses
67 -b: show the balance of addresses""",
69 'history':"Shows the transaction history",
70 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
72 """Create a signed transaction, password protected.
73 Syntax: mktx <recipient> <amount> [label]
74 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
77 "Print the generation seed of your wallet.",
79 'Imports a key pair\nSyntax: import <address>:<privatekey>',
81 '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 "',
83 '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 "',
85 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
87 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
89 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
98 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
100 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
102 if __name__ == '__main__':
104 # Load simple config class
105 simple_config = SimpleConfig()
107 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
108 parser = optparse.OptionParser(prog=usage)
109 parser.add_option("-g", "--gui", dest="gui", default=simple_config.config["gui"], help="gui")
110 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
111 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
112 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
113 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
114 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
115 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
116 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.")
117 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")
118 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
119 options, args = parser.parse_args()
121 proxy = parse_proxy_options(options.proxy) if options.proxy else simple_config.config["proxy"]
123 wallet.set_path(options.wallet_path)
129 elif len(args)==1 and re.match('^bitcoin:', args[0]):
134 firstarg = args[1] if len(args) > 1 else ''
136 #this entire if/else block is just concerned with importing the
137 #right GUI toolkit based the GUI command line option given
140 if options.gui=='gtk':
142 import lib.gui as gui
144 import electrum.gui as gui
145 elif options.gui=='qt':
147 import lib.gui_qt as gui
149 import electrum.gui_qt as gui
150 elif options.gui == 'lite':
152 import lib.gui_lite as gui
154 import electrum.gui_lite as gui
156 sys.exit("Error: Unknown GUI: " + options.gui)
158 gui = gui.ElectrumGui(wallet)
159 interface = WalletSynchronizer(wallet, True, gui.server_list_changed, proxy)
163 found = wallet.file_exists
165 found = gui.restore_or_create()
166 except SystemExit, e:
168 except BaseException, e:
170 traceback.print_exc(file=sys.stdout)
171 #gui.show_message(e.message)
180 if cmd not in known_commands:
183 if not wallet.file_exists and cmd not in ['help','create','restore']:
184 print "Error: Wallet file not found."
185 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
188 if cmd in ['create', 'restore']:
189 if wallet.file_exists:
190 sys.exit("Error: Remove the existing wallet first!")
191 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
193 w_host, w_port, w_protocol = wallet.server.split(':')
194 host = raw_input("server (default:%s):"%w_host)
195 port = raw_input("port (default:%s):"%w_port)
196 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
197 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
198 gap = raw_input("gap limit (default 5):")
199 if host: w_host = host
200 if port: w_port = port
201 if protocol: w_protocol = protocol
202 wallet.server = w_host + ':' + w_port + ':' +w_protocol
203 if fee: wallet.fee = float(fee)
204 if gap: wallet.gap_limit = int(gap)
207 seed = raw_input("seed:")
211 print_error("Warning: Not hex, trying decode.")
212 seed = mnemonic.mn_decode( seed.split(' ') )
214 sys.exit("Error: No seed")
216 wallet.seed = str(seed)
217 wallet.init_mpk( wallet.seed )
218 if not options.offline:
219 WalletSynchronizer(wallet, proxy=proxy).start()
220 print "Recovering wallet..."
221 wallet.up_to_date_event.clear()
222 wallet.up_to_date = False
224 if wallet.is_found():
225 print "Recovery successful"
227 print_error("Warning: Found no history for this wallet")
230 wallet.fill_addressbook()
232 print_error("Wallet saved in '" + wallet.path)
234 wallet.new_seed(None)
235 wallet.init_mpk( wallet.seed )
236 wallet.synchronize() # there is no wallet thread
238 print "Your wallet generation seed is: " + wallet.seed
239 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
240 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
241 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
242 print "Wallet saved in '%s'"%wallet.path
245 wallet.update_password(wallet.seed, None, password)
248 if cmd in ['payto', 'mktx']:
251 amount = int( 100000000 * Decimal(args[2]) )
253 label = ' '.join(args[3:])
255 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
261 if cmd not in offline_commands and not options.offline:
262 WalletSynchronizer(wallet, proxy=proxy).start()
266 # check if --from_addr not in wallet (for mktx/payto)
269 if options.from_addr:
270 from_addr = options.from_addr
271 if from_addr not in wallet.all_addresses():
275 if cmd=='addresses' and options.show_keys:
276 print "WARNING: ALL your private keys are secret."
277 print "Exposing a single private key can compromise your entire wallet!"
278 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
280 # commands needing password
281 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
282 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
285 wallet.pw_decode( wallet.seed, password)
287 print_error("Error: This password does not decode this wallet.")
291 # See if they specificed a key on the cmd line, if not prompt
295 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
297 wallet.import_key(keypair,password)
299 print "Keypair imported"
300 except BaseException, e:
301 print_error("Error: Keypair import failed: " + str(e))
305 if cmd2 not in known_commands:
307 print "Type 'electrum help <command>' to see the help for a specific command"
308 print "Type 'electrum --help' to see the list of options"
309 print "List of commands:", ', '.join(known_commands)
311 print known_commands[cmd2]
314 seed = wallet.pw_decode( wallet.seed, password)
315 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
317 elif cmd == 'deseed':
319 print_error("Error: This wallet has no seed")
320 elif wallet.use_encryption:
321 print_error("Error: This wallet is encrypted")
323 ns = wallet.path + '.seed'
324 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
325 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
327 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
330 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
334 print_error("Action canceled.")
336 elif cmd == 'reseed':
338 print "Warning: This wallet already has a seed", wallet.seed
340 ns = wallet.path + '.seed'
346 sys.exit("Error: Seed file not found")
349 d = ast.literal_eval( data )
351 imported_keys = d.get('imported_keys',{})
353 sys.exit("Error: Error with seed file")
355 mpk = wallet.master_public_key
357 wallet.imported_keys = imported_keys
358 wallet.use_encryption = False
359 wallet.init_mpk(seed)
360 if mpk == wallet.master_public_key:
362 print "Done: " + wallet.path
364 print_error("Error: Master public key does not match")
366 elif cmd == 'validateaddress':
368 print wallet.is_valid(addr)
370 elif cmd == 'balance':
376 c, u = wallet.get_balance()
378 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
380 print Decimal( c ) / 100000000
383 c, u = wallet.get_addr_balance(addr)
385 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
387 print "%s %s" % (addr, str(Decimal(c)/100000000))
389 elif cmd in [ 'contacts']:
390 for addr in wallet.addressbook:
391 print addr, " ", wallet.labels.get(addr)
397 elif cmd in [ 'addresses']:
398 for addr in wallet.all_addresses():
399 if options.show_all or not wallet.is_change(addr):
401 flags = wallet.get_address_flags(addr)
402 label = wallet.labels.get(addr,'')
404 if label: label = "\"%s\""%label
406 if options.show_balance:
407 h = wallet.history.get(addr,[])
410 # if item['is_input']: ni += 1
412 b = format_satoshis(wallet.get_addr_balance(addr)[0])
415 if options.show_keys:
416 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
417 print flags, m_addr, b, label
420 lines = wallet.get_tx_history()
427 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
429 print line['timestamp']
431 label = line.get('label')
432 if not label: label = line['tx_hash']
433 else: label = label + ' '*(64 - len(label) )
435 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
436 print "# balance: ", format_satoshis(b)
441 label = ' '.join(args[2:])
443 print_error("Error. Syntax: label <tx_hash> <text>")
445 wallet.labels[tx] = label
448 elif cmd in ['payto', 'mktx']:
449 if from_addr and is_temporary:
450 if from_addr.find(":") == -1:
451 keypair = from_addr + ":" + prompt_password('Private key:', False)
454 from_addr = keypair.split(':')[0]
455 if not wallet.import_key(keypair,password):
456 print_error("Error: Invalid key pair")
458 wallet.history[from_addr] = interface.retrieve_history(from_addr)
459 wallet.update_tx_history()
460 change_addr = from_addr
462 if options.change_addr:
463 change_addr = options.change_addr
465 for k, v in wallet.labels.items():
468 print "alias", to_address
470 if change_addr and v == change_addr:
473 tx = wallet.mktx( to_address, amount, label, password,
474 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
477 traceback.print_exc(file=sys.stdout)
480 if tx and cmd=='payto':
481 r, h = wallet.sendtx( tx )
487 wallet.imported_keys.pop(from_addr)
488 del(wallet.history[from_addr])
491 elif cmd == 'sendtx':
493 r, h = wallet.sendtx( tx )
496 elif cmd == 'password':
498 seed = wallet.pw_decode( wallet.seed, password)
500 sys.exit("Error: Password does not decrypt this wallet.")
502 new_password = prompt_password('New password:')
503 wallet.update_password(seed, password, new_password)
505 elif cmd == 'signmessage':
507 print_error("Error: Invalid usage of signmessage.")
508 print known_commands[cmd]
511 message = ' '.join(args[2:])
513 print "Warning: Message was reconstructed from several arguments:", repr(message)
514 print wallet.sign_message(address, message, password)
516 elif cmd == 'verifymessage':
520 message = ' '.join(args[3:])
522 print_error("Error: Not all parameters were given, displaying help instead.")
523 print known_commands[cmd]
526 print "Warning: Message was reconstructed from several arguments:", repr(message)
528 wallet.verify_message(address, signature, message)
530 except BaseException as e:
531 print "Verification error: {0}".format(e)
534 elif cmd == 'freeze':
536 print self.wallet.freeze(addr)
538 elif cmd == 'unfreeze':
540 print self.wallet.unfreeze(addr)
542 elif cmd == 'prioritize':
544 print self.wallet.prioritize(addr)
546 elif cmd == 'unprioritize':
548 print self.wallet.unprioritize(addr)