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 import lib as electrum
38 from optparse import OptionParser
39 from decimal import Decimal
40 from electrum import Wallet, WalletSynchronizer, format_satoshis
44 'validateaddress':'check that the address is valid',
45 'balance': "Display the balance of your wallet or of an address.\nsyntax: balance [<address>]",
46 'contacts': "Show your list of contacts",
47 'create':'create wallet',
48 'restore':'restore wallet',
50 payto <recipient> <amount> [label]
51 create and broadcast a transaction.
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
57 broadcast a transaction to the network. <tx> must be in hexadecimal""",
59 "change your password",
61 """show your list of addresses.
63 -a: show all addresses, including change addresses
65 -b: show the balance of addresses""",
66 'history':"show the transaction history",
67 'label':"assign a label to an item",
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.",
78 'sign a message with a key',
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="qt", 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
142 print "unknown gui", options.gui
145 gui = gui.ElectrumGui(wallet)
146 WalletSynchronizer(wallet,True).start()
149 found = wallet.file_exists
151 found = gui.restore_or_create()
152 except SystemExit, e:
154 except BaseException, e:
156 traceback.print_exc(file=sys.stdout)
157 #gui.show_message(e.message)
160 if not found: exit(1)
165 if cmd not in known_commands:
168 if not wallet.file_exists and cmd not in ['help','create','restore']:
169 print "Wallet file not found."
170 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
173 if cmd in ['create', 'restore']:
174 from electrum import mnemonic
175 if wallet.file_exists:
176 print "remove the existing wallet first!"
178 password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):")
180 password2 = getpass.getpass("Confirm password:")
181 if password != password2:
187 w_host, w_port, w_protocol = wallet.server.split(':')
188 host = raw_input("server (default:%s):"%w_host)
189 port = raw_input("port (default:%s):"%w_port)
190 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
191 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
192 gap = raw_input("gap limit (default 5):")
193 if host: w_host = host
194 if port: w_port = port
195 if protocol: w_protocol = protocol
196 wallet.server = w_host + ':' + w_port + ':' +w_protocol
197 if fee: wallet.fee = float(fee)
198 if gap: wallet.gap_limit = int(gap)
201 seed = raw_input("seed:")
205 print "not hex, trying decode"
206 seed = mnemonic.mn_decode( seed.split(' ') )
211 wallet.seed = str(seed)
212 wallet.init_mpk( wallet.seed )
213 if not options.offline:
214 WalletSynchronizer(wallet).start()
215 print "recovering wallet..."
216 wallet.up_to_date_event.clear()
217 wallet.up_to_date = False
219 if wallet.is_found():
220 print "recovery successful"
222 print "found no history for this wallet"
223 wallet.fill_addressbook()
225 print "Wallet saved in '%s'"%wallet.path
227 wallet.new_seed(None)
228 wallet.init_mpk( wallet.seed )
229 wallet.synchronize() # there is no wallet thread
231 print "Your wallet generation seed is: " + wallet.seed
232 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
233 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
234 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
235 print "Wallet saved in '%s'"%wallet.path
238 wallet.update_password(wallet.seed, None, password)
241 if cmd in ['payto', 'mktx']:
244 amount = int( 100000000 * Decimal(args[2]) )
246 label = ' '.join(args[3:])
248 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
254 if cmd not in offline_commands and not options.offline:
255 WalletSynchronizer(wallet).start()
259 # check if --from_addr not in wallet (for mktx/payto)
262 if options.from_addr:
263 from_addr = options.from_addr
264 if from_addr not in wallet.all_addresses():
267 # commands needing password
268 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
269 password = getpass.getpass('Password:') if wallet.use_encryption and not is_temporary else None
272 wallet.pw_decode( wallet.seed, password)
274 print "invalid password"
280 wallet.import_key(keypair,password)
282 print "keypair imported"
283 except BaseException, e:
284 print( 'Error:' + str(e) )
288 if cmd2 not in known_commands:
289 print "type 'electrum help <command>' to see the help for a specific command"
290 print "type 'electrum --help' to see the list of options"
291 print "list of commands:", ', '.join(known_commands)
293 print known_commands[cmd2]
296 from electrum import mnemonic
297 seed = wallet.pw_decode( wallet.seed, password)
298 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
300 elif cmd == 'deseed':
302 print "Error: This wallet has no seed"
303 elif wallet.use_encryption:
304 print "Error: This wallet is encrypted"
306 ns = wallet.path + '.seed'
307 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
308 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
310 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
313 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
317 print "Action canceled."
319 elif cmd == 'reseed':
321 print "This wallet already has a seed", wallet.seed
323 ns = wallet.path + '.seed'
329 print "seed file not found"
333 d = ast.literal_eval( data )
335 imported_keys = d.get('imported_keys',{})
337 print "error with seed file"
340 mpk = wallet.master_public_key
342 wallet.imported_keys = imported_keys
343 wallet.use_encryption = False
344 wallet.init_mpk(seed)
345 if mpk == wallet.master_public_key:
347 print "Done: " + wallet.path
349 print "error: master public key does not match"
351 elif cmd == 'validateaddress':
353 print wallet.is_valid(addr)
355 elif cmd == 'balance':
361 c, u = wallet.get_balance()
363 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
365 print Decimal( c ) / 100000000
368 c, u = wallet.get_addr_balance(addr)
370 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
372 print "%s %s" % (addr, str(Decimal(c)/100000000))
374 elif cmd in [ 'contacts']:
375 for addr in wallet.addressbook:
376 print addr, " ", wallet.labels.get(addr)
382 elif cmd in [ 'addresses']:
383 for addr in wallet.all_addresses():
384 if options.show_all or not wallet.is_change(addr):
385 label = wallet.labels.get(addr)
387 if wallet.is_change(addr): _type = "[change]"
388 if addr in wallet.imported_keys.keys(): _type = "[imported]"
389 if label is None: label = ''
390 if options.show_balance:
391 h = wallet.history.get(addr,[])
394 if item['is_input']: ni += 1
396 b = "%d %d %s"%(no, ni, str(Decimal(wallet.get_addr_balance(addr)[0])/100000000))
398 if options.show_keys:
399 addr += ':' + str(wallet.get_private_key_base58(addr, password))
400 print addr, b, _type, label
403 lines = wallet.get_tx_history()
410 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
412 print line['timestamp']
414 label = line.get('label')
415 if not label: label = line['tx_hash']
416 else: label = label + ' '*(64 - len(label) )
418 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
419 print "# balance: ", format_satoshis(b)
424 label = ' '.join(args[2:])
426 print "syntax: label <tx_hash> <text>"
428 wallet.labels[tx] = label
431 elif cmd in ['payto', 'mktx']:
432 if from_addr and is_temporary:
433 if from_addr.find(":") == -1:
434 keypair = from_addr + ":" + getpass.getpass('Private key:')
437 from_addr = keypair.split(':')[0]
438 if not wallet.import_key(keypair,password):
439 print "invalid key pair"
441 wallet.history[from_addr] = interface.retrieve_history(from_addr)
442 wallet.update_tx_history()
443 change_addr = from_addr
445 if options.change_addr:
446 change_addr = options.change_addr
448 for k, v in wallet.labels.items():
451 print "alias", to_address
453 if change_addr and v == change_addr:
456 tx = wallet.mktx( to_address, amount, label, password,
457 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
460 traceback.print_exc(file=sys.stdout)
463 if tx and cmd=='payto':
464 r, h = wallet.sendtx( tx )
470 wallet.imported_keys.pop(from_addr)
471 del(wallet.history[from_addr])
474 elif cmd == 'sendtx':
476 r, h = wallet.sendtx( tx )
479 elif cmd == 'password':
481 seed = wallet.pw_decode( wallet.seed, password)
485 new_password = getpass.getpass('New password:')
486 if new_password == getpass.getpass('Confirm new password:'):
487 wallet.update_password(seed, password, new_password)
489 print "error: mismatch"
491 elif cmd == 'signmessage':
492 address, message = args[1:3]
493 print wallet.sign_message(address, message, password)
495 elif cmd == 'verifymessage':
496 address, signature, message = args[1:4]
498 wallet.verify_message(address, signature, message)
503 elif cmd == 'freeze':
505 if addr in self.wallet.all_addresses() and addr not in self.wallet.frozen_addresses:
506 self.wallet.frozen_addresses.append(addr)
512 elif cmd == 'unfreeze':
514 if addr in self.wallet.all_addresses() and addr in self.wallet.frozen_addresses:
515 self.wallet.frozen_addresses.remove(addr)
521 elif cmd == 'prioritize':
523 if addr in self.wallet.all_addresses() and addr not in self.wallet.frozen_addresses:
524 self.wallet.prioritized_addresses.append(addr)
530 elif cmd == 'unprioritize':
532 if addr in self.wallet.all_addresses() and addr in self.wallet.frozen_addresses:
533 self.wallet.prioritized_addresses.remove(addr)