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 sys.stderr.write("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'\n")
31 sys.stderr.write("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'\n")
36 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
38 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
40 from optparse import OptionParser
41 from decimal import Decimal
44 'help':'Prints this help',
45 'validateaddress':'Check that the address is valid',
46 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
47 'contacts': "Show your list of contacts",
48 'create':'Create a wallet',
49 'restore':'Restore a wallet',
50 'payto':"""Create and broadcast a transaction.
51 Syntax: payto <recipient> <amount> [label]
52 <recipient> can be a bitcoin address or a label
53 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
56 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
58 "Changes your password",
60 """Shows your list of addresses.
62 -a: show all addresses, including change addresses
64 -b: show the balance of addresses""",
66 'history':"Shows the transaction history",
67 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
69 """Create a signed transaction, password protected.
70 Syntax: mktx <recipient> <amount> [label]
71 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
74 "Print the generation seed of your wallet.",
76 'Imports a key pair\nSyntax: import <address>:<privatekey>',
78 'Signs a message with a key\nSyntax: signmessage <address> <message>',
80 'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>',
82 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
84 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
86 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
95 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
97 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
99 if __name__ == '__main__':
101 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
102 parser = OptionParser(usage=usage)
103 parser.add_option("-g", "--gui", dest="gui", default="lite", help="gui")
104 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
105 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
106 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
107 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
108 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
109 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
110 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.")
111 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")
112 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
113 options, args = parser.parse_args()
116 wallet.set_path(options.wallet_path)
118 wallet.remote_url = options.remote_url
123 elif len(args)==1 and re.match('^bitcoin:', args[0]):
128 firstarg = args[1] if len(args) > 1 else ''
131 if options.gui=='gtk':
133 import lib.gui as gui
135 import electrum.gui as gui
136 elif options.gui=='qt':
138 import lib.gui_qt as gui
140 import electrum.gui_qt as gui
141 elif options.gui == 'lite':
143 import lib.gui_lite as gui
145 import electrum.gui_lite as gui
147 sys.stderr.write("Error: Unknown GUI: " + options.gui + "\n")
151 gui = gui.ElectrumGui(wallet)
152 WalletSynchronizer(wallet,True).start()
155 found = wallet.file_exists
157 found = gui.restore_or_create()
158 except SystemExit, e:
160 except BaseException, e:
162 traceback.print_exc(file=sys.stdout)
163 #gui.show_message(e.message)
172 if cmd not in known_commands:
175 if not wallet.file_exists and cmd not in ['help','create','restore']:
176 sys.stderr.write("Error: Wallet file not found.\n")
177 sys.stderr.write("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option\n")
181 if cmd in ['create', 'restore']:
182 if wallet.file_exists:
183 sys.stderr.write("Error: Remove the existing wallet first!\n")
186 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
188 w_host, w_port, w_protocol = wallet.server.split(':')
189 host = raw_input("server (default:%s):"%w_host)
190 port = raw_input("port (default:%s):"%w_port)
191 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
192 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
193 gap = raw_input("gap limit (default 5):")
194 if host: w_host = host
195 if port: w_port = port
196 if protocol: w_protocol = protocol
197 wallet.server = w_host + ':' + w_port + ':' +w_protocol
198 if fee: wallet.fee = float(fee)
199 if gap: wallet.gap_limit = int(gap)
202 seed = raw_input("seed:")
206 sys.stderr.write("Warning: Not hex, trying decode.\n")
208 seed = mnemonic.mn_decode( seed.split(' ') )
210 sys.stderr.write("Error: No seed\n")
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 sys.stderr.write("Warning: Found no history for this wallet\n")
227 wallet.fill_addressbook()
229 sys.stderr.write("Wallet saved in '" + wallet.path + "'\n")
232 wallet.new_seed(None)
233 wallet.init_mpk( wallet.seed )
234 wallet.synchronize() # there is no wallet thread
236 print "Your wallet generation seed is: " + wallet.seed
237 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
238 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
239 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
240 print "Wallet saved in '%s'"%wallet.path
243 wallet.update_password(wallet.seed, None, password)
246 if cmd in ['payto', 'mktx']:
249 amount = int( 100000000 * Decimal(args[2]) )
251 label = ' '.join(args[3:])
253 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
259 if cmd not in offline_commands and not options.offline:
260 WalletSynchronizer(wallet).start()
264 # check if --from_addr not in wallet (for mktx/payto)
267 if options.from_addr:
268 from_addr = options.from_addr
269 if from_addr not in wallet.all_addresses():
272 # commands needing password
273 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
274 password = prompt_password('Password:') if wallet.use_encryption and not is_temporary else None
277 wallet.pw_decode( wallet.seed, password)
279 sys.stderr.write("Error: This password does not decode this wallet.\n")
286 wallet.import_key(keypair,password)
288 print "Keypair imported"
289 except BaseException, e:
290 sys.stderr.write("Error: Keypair import failed: " + str(e) + "\n")
295 if cmd2 not in known_commands:
296 print "Type 'electrum help <command>' to see the help for a specific command"
297 print "Type 'electrum --help' to see the list of options"
298 print "List of commands:", ', '.join(known_commands)
300 print known_commands[cmd2]
303 seed = wallet.pw_decode( wallet.seed, password)
304 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
306 elif cmd == 'deseed':
308 sys.stderr.write("Error: This wallet has no seed\n")
310 elif wallet.use_encryption:
311 sys.stderr.write("Error: This wallet is encrypted\n")
314 ns = wallet.path + '.seed'
315 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
316 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
318 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
321 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
325 sys.stderr.write("Action canceled.\n")
328 elif cmd == 'reseed':
330 print "Warning: This wallet already has a seed", wallet.seed
332 ns = wallet.path + '.seed'
338 sys.stderr.write("Error: Seed file not found\n")
343 d = ast.literal_eval( data )
345 imported_keys = d.get('imported_keys',{})
347 sys.stderr.write("Error: Error with seed file\n")
351 mpk = wallet.master_public_key
353 wallet.imported_keys = imported_keys
354 wallet.use_encryption = False
355 wallet.init_mpk(seed)
356 if mpk == wallet.master_public_key:
358 print "Done: " + wallet.path
360 sys.stderr.write("Error: Master public key does not match\n")
363 elif cmd == 'validateaddress':
365 print wallet.is_valid(addr)
367 elif cmd == 'balance':
373 c, u = wallet.get_balance()
375 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
377 print Decimal( c ) / 100000000
380 c, u = wallet.get_addr_balance(addr)
382 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
384 print "%s %s" % (addr, str(Decimal(c)/100000000))
386 elif cmd in [ 'contacts']:
387 for addr in wallet.addressbook:
388 print addr, " ", wallet.labels.get(addr)
394 elif cmd in [ 'addresses']:
395 for addr in wallet.all_addresses():
396 if options.show_all or not wallet.is_change(addr):
398 flags = wallet.get_address_flags(addr)
399 label = wallet.labels.get(addr,'')
401 if label: label = "\"%s\""%label
403 if options.show_balance:
404 h = wallet.history.get(addr,[])
407 # if item['is_input']: ni += 1
409 b = format_satoshis(wallet.get_addr_balance(addr)[0])
412 if options.show_keys:
413 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
414 print flags, m_addr, b, label
417 lines = wallet.get_tx_history()
424 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
426 print line['timestamp']
428 label = line.get('label')
429 if not label: label = line['tx_hash']
430 else: label = label + ' '*(64 - len(label) )
432 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
433 print "# balance: ", format_satoshis(b)
438 label = ' '.join(args[2:])
440 sys.stderr.write("Error. Syntax: label <tx_hash> <text>\n")
443 wallet.labels[tx] = label
446 elif cmd in ['payto', 'mktx']:
447 if from_addr and is_temporary:
448 if from_addr.find(":") == -1:
449 keypair = from_addr + ":" + prompt_password('Private key:', False)
452 from_addr = keypair.split(':')[0]
453 if not wallet.import_key(keypair,password):
454 sys.stderr.write("Error: Invalid key pair\n")
457 wallet.history[from_addr] = interface.retrieve_history(from_addr)
458 wallet.update_tx_history()
459 change_addr = from_addr
461 if options.change_addr:
462 change_addr = options.change_addr
464 for k, v in wallet.labels.items():
467 print "alias", to_address
469 if change_addr and v == change_addr:
472 tx = wallet.mktx( to_address, amount, label, password,
473 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
476 traceback.print_exc(file=sys.stdout)
479 if tx and cmd=='payto':
480 r, h = wallet.sendtx( tx )
486 wallet.imported_keys.pop(from_addr)
487 del(wallet.history[from_addr])
490 elif cmd == 'sendtx':
492 r, h = wallet.sendtx( tx )
495 elif cmd == 'password':
497 seed = wallet.pw_decode( wallet.seed, password)
499 sys.stderr.write("Error: Password does not decrypt this wallet.\n")
503 new_password = prompt_password('New password:')
504 wallet.update_password(seed, password, new_password)
506 elif cmd == 'signmessage':
508 message = ' '.join(args[2:])
510 print "Warning: Message was reconstructed from several arguments:", repr(message)
511 print wallet.sign_message(address, message, password)
513 elif cmd == 'verifymessage':
517 message = ' '.join(args[3:])
519 sys.stderr.write("Error: Not all parameters were given, displaying help instead.\n")
521 print known_commands[cmd]
524 print "Warning: Message was reconstructed from several arguments:", repr(message)
526 wallet.verify_message(address, signature, message)
531 elif cmd == 'freeze':
533 print self.wallet.freeze(addr)
535 elif cmd == 'unfreeze':
537 print self.wallet.unfreeze(addr)
539 elif cmd == 'prioritize':
541 print self.wallet.prioritize(addr)
543 elif cmd == 'unprioritize':
545 print self.wallet.unprioritize(addr)