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/>.
19 import re, sys, getpass
24 print "python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'"
30 print "AES does not seem to be installed. Try 'sudo pip install slowaes'"
34 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic
36 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic
38 from optparse import OptionParser
39 from decimal import Decimal
42 'help':'Prints this help',
43 'validateaddress':'Check that the address is valid',
44 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
45 'contacts': "Show your list of contacts",
46 'create':'Create a wallet',
47 'restore':'Restore a wallet',
48 'payto':"""Create and broadcast a transaction.
49 Syntax: payto <recipient> <amount> [label]
50 <recipient> can be a bitcoin address or a label
51 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
54 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
56 "Changes your password",
58 """Shows your list of addresses.
60 -a: show all addresses, including change addresses
62 -b: show the balance of addresses""",
64 'history':"Shows the transaction history",
65 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
67 """Create a signed transaction, password protected.
68 Syntax: mktx <recipient> <amount> [label]
69 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
72 "Print the generation seed of your wallet.",
74 'Imports a key pair\nSyntax: import <address>:<privatekey>',
76 'Signs a message with a key\nSyntax: signmessage <address> <message>',
78 'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>',
80 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
82 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
84 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
93 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
95 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
97 if __name__ == '__main__':
99 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
100 parser = OptionParser(usage=usage)
101 parser.add_option("-g", "--gui", dest="gui", default="qt", help="gui")
102 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
103 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
104 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
105 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
106 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
107 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
108 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.")
109 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")
110 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
111 options, args = parser.parse_args()
114 wallet.set_path(options.wallet_path)
116 wallet.remote_url = options.remote_url
121 elif len(args)==1 and re.match('^bitcoin:', args[0]):
126 firstarg = args[1] if len(args) > 1 else ''
129 if options.gui=='gtk':
131 import lib.gui as gui
133 import electrum.gui as gui
134 elif options.gui=='qt':
136 import lib.gui_qt as gui
138 import electrum.gui_qt as gui
140 print "unknown gui", options.gui
143 gui = gui.ElectrumGui(wallet)
144 WalletSynchronizer(wallet,True).start()
147 found = wallet.file_exists
149 found = gui.restore_or_create()
150 except SystemExit, e:
152 except BaseException, e:
154 traceback.print_exc(file=sys.stdout)
155 #gui.show_message(e.message)
158 if not found: exit(1)
163 if cmd not in known_commands:
166 if not wallet.file_exists and cmd not in ['help','create','restore']:
167 print "Wallet file not found."
168 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
171 if cmd in ['create', 'restore']:
172 if wallet.file_exists:
173 print "remove the existing wallet first!"
175 password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):")
177 password2 = getpass.getpass("Confirm password:")
178 if password != password2:
184 w_host, w_port, w_protocol = wallet.server.split(':')
185 host = raw_input("server (default:%s):"%w_host)
186 port = raw_input("port (default:%s):"%w_port)
187 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
188 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
189 gap = raw_input("gap limit (default 5):")
190 if host: w_host = host
191 if port: w_port = port
192 if protocol: w_protocol = protocol
193 wallet.server = w_host + ':' + w_port + ':' +w_protocol
194 if fee: wallet.fee = float(fee)
195 if gap: wallet.gap_limit = int(gap)
198 seed = raw_input("seed:")
202 print "not hex, trying decode"
203 seed = mnemonic.mn_decode( seed.split(' ') )
208 wallet.seed = str(seed)
209 wallet.init_mpk( wallet.seed )
210 if not options.offline:
211 WalletSynchronizer(wallet).start()
212 print "recovering wallet..."
213 wallet.up_to_date_event.clear()
214 wallet.up_to_date = False
216 if wallet.is_found():
217 print "recovery successful"
219 print "found no history for this wallet"
220 wallet.fill_addressbook()
222 print "Wallet saved in '%s'"%wallet.path
224 wallet.new_seed(None)
225 wallet.init_mpk( wallet.seed )
226 wallet.synchronize() # there is no wallet thread
228 print "Your wallet generation seed is: " + wallet.seed
229 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
230 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
231 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
232 print "Wallet saved in '%s'"%wallet.path
235 wallet.update_password(wallet.seed, None, password)
238 if cmd in ['payto', 'mktx']:
241 amount = int( 100000000 * Decimal(args[2]) )
243 label = ' '.join(args[3:])
245 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
251 if cmd not in offline_commands and not options.offline:
252 WalletSynchronizer(wallet).start()
256 # check if --from_addr not in wallet (for mktx/payto)
259 if options.from_addr:
260 from_addr = options.from_addr
261 if from_addr not in wallet.all_addresses():
264 # commands needing password
265 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
266 password = getpass.getpass('Password:') if wallet.use_encryption and not is_temporary else None
269 wallet.pw_decode( wallet.seed, password)
271 print "invalid password"
277 wallet.import_key(keypair,password)
279 print "keypair imported"
280 except BaseException, e:
281 print( 'Error:' + str(e) )
285 if cmd2 not in known_commands:
286 print "type 'electrum help <command>' to see the help for a specific command"
287 print "type 'electrum --help' to see the list of options"
288 print "list of commands:", ', '.join(known_commands)
290 print known_commands[cmd2]
293 seed = wallet.pw_decode( wallet.seed, password)
294 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
296 elif cmd == 'deseed':
298 print "Error: This wallet has no seed"
299 elif wallet.use_encryption:
300 print "Error: This wallet is encrypted"
302 ns = wallet.path + '.seed'
303 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
304 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
306 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
309 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
313 print "Action canceled."
315 elif cmd == 'reseed':
317 print "This wallet already has a seed", wallet.seed
319 ns = wallet.path + '.seed'
325 print "seed file not found"
329 d = ast.literal_eval( data )
331 imported_keys = d.get('imported_keys',{})
333 print "error with seed file"
336 mpk = wallet.master_public_key
338 wallet.imported_keys = imported_keys
339 wallet.use_encryption = False
340 wallet.init_mpk(seed)
341 if mpk == wallet.master_public_key:
343 print "Done: " + wallet.path
345 print "error: master public key does not match"
347 elif cmd == 'validateaddress':
349 print wallet.is_valid(addr)
351 elif cmd == 'balance':
357 c, u = wallet.get_balance()
359 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
361 print Decimal( c ) / 100000000
364 c, u = wallet.get_addr_balance(addr)
366 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
368 print "%s %s" % (addr, str(Decimal(c)/100000000))
370 elif cmd in [ 'contacts']:
371 for addr in wallet.addressbook:
372 print addr, " ", wallet.labels.get(addr)
378 elif cmd in [ 'addresses']:
379 for addr in wallet.all_addresses():
380 if options.show_all or not wallet.is_change(addr):
382 flags = wallet.get_address_flags(addr)
383 label = wallet.labels.get(addr,'')
385 if label: label = "\"%s\""%label
387 if options.show_balance:
388 h = wallet.history.get(addr,[])
391 # if item['is_input']: ni += 1
393 b = format_satoshis(wallet.get_addr_balance(addr)[0])
396 if options.show_keys:
397 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
398 print flags, m_addr, b, label
401 lines = wallet.get_tx_history()
408 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
410 print line['timestamp']
412 label = line.get('label')
413 if not label: label = line['tx_hash']
414 else: label = label + ' '*(64 - len(label) )
416 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
417 print "# balance: ", format_satoshis(b)
422 label = ' '.join(args[2:])
424 print "syntax: label <tx_hash> <text>"
426 wallet.labels[tx] = label
429 elif cmd in ['payto', 'mktx']:
430 if from_addr and is_temporary:
431 if from_addr.find(":") == -1:
432 keypair = from_addr + ":" + getpass.getpass('Private key:')
435 from_addr = keypair.split(':')[0]
436 if not wallet.import_key(keypair,password):
437 print "invalid key pair"
439 wallet.history[from_addr] = interface.retrieve_history(from_addr)
440 wallet.update_tx_history()
441 change_addr = from_addr
443 if options.change_addr:
444 change_addr = options.change_addr
446 for k, v in wallet.labels.items():
449 print "alias", to_address
451 if change_addr and v == change_addr:
454 tx = wallet.mktx( to_address, amount, label, password,
455 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
458 traceback.print_exc(file=sys.stdout)
461 if tx and cmd=='payto':
462 r, h = wallet.sendtx( tx )
468 wallet.imported_keys.pop(from_addr)
469 del(wallet.history[from_addr])
472 elif cmd == 'sendtx':
474 r, h = wallet.sendtx( tx )
477 elif cmd == 'password':
479 seed = wallet.pw_decode( wallet.seed, password)
483 new_password = getpass.getpass('New password:')
484 if new_password == getpass.getpass('Confirm new password:'):
485 wallet.update_password(seed, password, new_password)
487 print "error: mismatch"
489 elif cmd == 'signmessage':
491 message = ' '.join(args[2:])
493 print "Warning: Message was reconstructed from several arguments:", repr(message)
494 print wallet.sign_message(address, message, password)
496 elif cmd == 'verifymessage':
500 message = ' '.join(args[3:])
502 print "Not all parameters were given, displaying help instead."
503 print known_commands[cmd]
506 print "Warning: Message was reconstructed from several arguments:", repr(message)
508 wallet.verify_message(address, signature, message)
513 elif cmd == 'freeze':
515 print self.wallet.freeze(addr)
517 elif cmd == 'unfreeze':
519 print self.wallet.unfreeze(addr)
521 elif cmd == 'prioritize':
523 print self.wallet.prioritize(addr)
525 elif cmd == 'unprioritize':
527 print self.wallet.unprioritize(addr)