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'"
35 from optparse import OptionParser
36 from decimal import Decimal
38 from electrum import Wallet, WalletSynchronizer, format_satoshis
40 known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'restore', 'payto', 'sendtx', 'password', 'addresses', 'history', 'label', 'mktx','seed','import','signmessage','verifymessage','eval','deseed','reseed']
41 offline_commands = ['password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed']
42 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
44 if __name__ == '__main__':
46 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
47 parser = OptionParser(usage=usage)
48 parser.add_option("-g", "--gui", dest="gui", default="qt", help="gui")
49 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
50 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
51 parser.add_option("-m", "--mnemonic", action="store_true", dest="show_mnemonic", default=False, help="[seed] print the seed as mnemonic")
52 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
53 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
54 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
55 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
56 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.")
57 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")
58 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
59 options, args = parser.parse_args()
62 wallet.set_path(options.wallet_path)
64 wallet.remote_url = options.remote_url
69 elif len(args)==1 and re.match('^bitcoin:', args[0]):
74 firstarg = args[1] if len(args) > 1 else ''
77 if options.gui=='gtk':
78 import electrum.gui as gui
79 elif options.gui=='qt':
80 import electrum.gui_qt as gui
82 print "unknown gui", options.gui
85 gui = gui.ElectrumGui(wallet)
86 WalletSynchronizer(wallet,True).start()
89 found = wallet.file_exists
91 found = gui.restore_or_create()
94 except BaseException, e:
96 traceback.print_exc(file=sys.stdout)
97 #gui.show_message(e.message)
100 if not found: exit(1)
105 if cmd not in known_commands:
108 if not wallet.file_exists and cmd not in ['help','create','restore']:
109 print "Wallet file not found."
110 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
113 if cmd in ['create', 'restore']:
114 from electrum import mnemonic
115 if wallet.file_exists:
116 print "remove the existing wallet first!"
118 password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):")
120 password2 = getpass.getpass("Confirm password:")
121 if password != password2:
127 w_host, w_port, w_protocol = wallet.server.split(':')
128 host = raw_input("server (default:%s):"%w_host)
129 port = raw_input("port (default:%s):"%w_port)
130 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
131 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
132 gap = raw_input("gap limit (default 5):")
133 if host: w_host = host
134 if port: w_port = port
135 if protocol: w_protocol = protocol
136 wallet.server = w_host + ':' + w_port + ':' +w_protocol
137 if fee: wallet.fee = float(fee)
138 if gap: wallet.gap_limit = int(gap)
141 seed = raw_input("seed:")
145 print "not hex, trying decode"
146 seed = mnemonic.mn_decode( seed.split(' ') )
151 wallet.seed = str(seed)
152 wallet.init_mpk( wallet.seed )
153 if not options.offline:
154 WalletSynchronizer(wallet).start()
155 print "recovering wallet..."
156 wallet.up_to_date_event.clear()
157 wallet.up_to_date = False
159 if wallet.is_found():
160 print "recovery successful"
162 print "found no history for this wallet"
163 wallet.fill_addressbook()
165 print "Wallet saved in '%s'"%options.wallet_path
167 wallet.new_seed(None)
168 wallet.init_mpk( wallet.seed )
169 wallet.synchronize() # there is no wallet thread
171 print "Your wallet generation seed is: " + wallet.seed
172 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
173 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
174 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
175 print "Wallet saved in '%s'"%wallet.path
178 if cmd in ['payto', 'mktx']:
181 amount = int( 100000000 * Decimal(args[2]) )
183 label = ' '.join(args[3:])
185 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
191 if cmd not in offline_commands and not options.offline:
192 WalletSynchronizer(wallet).start()
196 # check if --from_addr not in wallet (for mktx/payto)
199 if options.from_addr:
200 from_addr = options.from_addr
201 if from_addr not in wallet.all_addresses():
204 # commands needing password
205 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
206 password = getpass.getpass('Password:') if wallet.use_encryption and not is_temporary else None
209 wallet.pw_decode( wallet.seed, password)
211 print "invalid password"
216 if wallet.import_key(keypair,password):
217 print "keypair imported"
224 if cmd2 not in known_commands:
225 print "type 'electrum help <command>' to see the help for a specific command"
226 print "type 'electrum --help' to see the list of options"
227 print "list of commands:", ', '.join(known_commands)
228 elif cmd2 == 'balance':
229 print "Display the balance of your wallet or of an address."
230 print "syntax: balance [<address>]"
231 elif cmd2 == 'contacts':
232 print "show your list of contacts"
233 elif cmd2 == 'payto':
234 print "payto <recipient> <amount> [label]"
235 print "create and broadcast a transaction."
236 print "<recipient> can be a bitcoin address or a label"
237 print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address"
238 elif cmd2== 'sendtx':
240 print "broadcast a transaction to the network. <tx> must be in hexadecimal"
241 elif cmd2 == 'password':
242 print "change your password"
243 elif cmd2 == 'addresses':
244 print "show your list of addresses."
245 print "options:\n -a: show all addresses, including change addresses\n-k: show private keys\n-b: show the balance of addresses"
246 elif cmd2 == 'history':
247 print "show the transaction history"
248 elif cmd2 == 'label':
249 print "assign a label to an item"
251 print "start the GUI"
253 print "create a signed transaction. password protected"
254 print "syntax: mktx <recipient> <amount> [label]"
255 print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address"
257 print "print the generation seed of your wallet."
258 print "options:\n-m, --mnemonic : print the seed as mnemonic"
259 elif cmd2 == 'deseed':
260 print "remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'"
261 elif cmd2 == 'reseed':
262 print "restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key."
264 print "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\""
267 from electrum import mnemonic
268 seed = wallet.pw_decode( wallet.seed, password)
269 if options.show_mnemonic:
270 print ' '.join(mnemonic.mn_encode(seed))
274 elif cmd == 'deseed':
276 print "Error: This wallet has no seed"
277 elif wallet.use_encryption:
278 print "Error: This wallet is encrypted"
280 ns = options.wallet_path+'.seed'
281 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(options.wallet_path,ns)
282 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
290 print "Action canceled."
292 elif cmd == 'reseed':
294 print "This wallet already has a seed"
296 ns = options.wallet_path+'.seed'
302 print "seed file not found"
305 mpk = wallet.master_public_key
307 wallet.use_encryption = False
308 wallet.init_mpk(seed)
309 if mpk == wallet.master_public_key:
313 print "error: master public key does not match"
315 elif cmd == 'validateaddress':
317 print wallet.is_valid(addr)
319 elif cmd == 'balance':
325 c, u = wallet.get_balance()
327 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
329 print Decimal( c ) / 100000000
332 c, u = wallet.get_addr_balance(addr)
334 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
336 print "%s %s" % (addr, str(Decimal(c)/100000000))
338 elif cmd in [ 'contacts']:
339 for addr in wallet.addressbook:
340 print addr, " ", wallet.labels.get(addr)
346 elif cmd in [ 'addresses']:
347 for addr in wallet.all_addresses():
348 if options.show_all or not wallet.is_change(addr):
349 label = wallet.labels.get(addr)
351 if wallet.is_change(addr): _type = "[change]"
352 if addr in wallet.imported_keys.keys(): _type = "[imported]"
353 if label is None: label = ''
354 if options.show_balance:
355 h = wallet.history.get(addr,[])
358 if item['is_input']: ni += 1
360 b = "%d %d %s"%(no, ni, str(Decimal(wallet.get_addr_balance(addr)[0])/100000000))
362 if options.show_keys:
363 addr += ':' + str(wallet.get_private_key_base58(addr, password))
364 print addr, b, _type, label
367 lines = wallet.get_tx_history()
374 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
376 print line['timestamp']
378 label = line.get('label')
379 if not label: label = line['tx_hash']
380 else: label = label + ' '*(64 - len(label) )
382 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
383 print "# balance: ", format_satoshis(b)
388 label = ' '.join(args[2:])
390 print "syntax: label <tx_hash> <text>"
392 wallet.labels[tx] = label
395 elif cmd in ['payto', 'mktx']:
396 if from_addr and is_temporary:
397 if from_addr.find(":") == -1:
398 keypair = from_addr + ":" + getpass.getpass('Private key:')
401 from_addr = keypair.split(':')[0]
402 if not wallet.import_key(keypair,password):
403 print "invalid key pair"
405 wallet.history[from_addr] = interface.retrieve_history(from_addr)
406 wallet.update_tx_history()
407 change_addr = from_addr
409 if options.change_addr:
410 change_addr = options.change_addr
412 for k, v in wallet.labels.items():
415 print "alias", to_address
417 if change_addr and v == change_addr:
420 tx = wallet.mktx( to_address, amount, label, password,
421 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
424 traceback.print_exc(file=sys.stdout)
427 if tx and cmd=='payto':
428 r, h = wallet.sendtx( tx )
434 wallet.imported_keys.pop(from_addr)
435 del(wallet.history[from_addr])
438 elif cmd == 'sendtx':
440 r, h = wallet.sendtx( tx )
443 elif cmd == 'password':
445 seed = wallet.pw_decode( wallet.seed, password)
449 new_password = getpass.getpass('New password:')
450 if new_password == getpass.getpass('Confirm new password:'):
451 wallet.use_encryption = (new_password != '')
452 wallet.seed = wallet.pw_encode( seed, new_password)
453 for k in wallet.imported_keys.keys():
454 a = wallet.imported_keys[k]
455 b = wallet.pw_decode(a, password)
456 c = wallet.pw_encode(b, new_password)
457 wallet.imported_keys[k] = c
460 print "error: mismatch"
462 elif cmd == 'signmessage':
463 address, message = args[1:3]
464 print wallet.sign_message(address, message, password)
466 elif cmd == 'verifymessage':
467 address, signature, message = args[1:4]
469 wallet.verify_message(address, signature, message)