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("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
52 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
53 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
54 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
55 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.")
56 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")
57 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
58 options, args = parser.parse_args()
61 wallet.set_path(options.wallet_path)
63 wallet.remote_url = options.remote_url
68 elif len(args)==1 and re.match('^bitcoin:', args[0]):
73 firstarg = args[1] if len(args) > 1 else ''
76 if options.gui=='gtk':
77 import electrum.gui as gui
78 elif options.gui=='qt':
79 import electrum.gui_qt as gui
81 print "unknown gui", options.gui
84 gui = gui.ElectrumGui(wallet)
85 WalletSynchronizer(wallet,True).start()
88 found = wallet.file_exists
90 found = gui.restore_or_create()
93 except BaseException, e:
95 traceback.print_exc(file=sys.stdout)
96 #gui.show_message(e.message)
104 if cmd not in known_commands:
107 if not wallet.file_exists and cmd not in ['help','create','restore']:
108 print "Wallet file not found."
109 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
112 if cmd in ['create', 'restore']:
113 from electrum import mnemonic
114 if wallet.file_exists:
115 print "remove the existing wallet first!"
117 password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):")
119 password2 = getpass.getpass("Confirm password:")
120 if password != password2:
126 w_host, w_port, w_protocol = wallet.server.split(':')
127 host = raw_input("server (default:%s):"%w_host)
128 port = raw_input("port (default:%s):"%w_port)
129 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
130 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
131 gap = raw_input("gap limit (default 5):")
132 if host: w_host = host
133 if port: w_port = port
134 if protocol: w_protocol = protocol
135 wallet.server = w_host + ':' + w_port + ':' +w_protocol
136 if fee: wallet.fee = float(fee)
137 if gap: wallet.gap_limit = int(gap)
140 seed = raw_input("seed:")
144 print "not hex, trying decode"
145 seed = mnemonic.mn_decode( seed.split(' ') )
150 wallet.seed = str(seed)
151 wallet.init_mpk( wallet.seed )
152 if not options.offline:
153 WalletSynchronizer(wallet).start()
154 print "recovering wallet..."
155 wallet.up_to_date_event.clear()
156 wallet.up_to_date = False
158 if wallet.is_found():
159 print "recovery successful"
161 print "found no history for this wallet"
162 wallet.fill_addressbook()
164 print "Wallet saved in '%s'"%wallet.path
166 wallet.new_seed(None)
167 wallet.init_mpk( wallet.seed )
168 wallet.synchronize() # there is no wallet thread
170 print "Your wallet generation seed is: " + wallet.seed
171 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
172 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
173 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
174 print "Wallet saved in '%s'"%wallet.path
177 wallet.update_password(wallet.seed, None, password)
180 if cmd in ['payto', 'mktx']:
183 amount = int( 100000000 * Decimal(args[2]) )
185 label = ' '.join(args[3:])
187 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
193 if cmd not in offline_commands and not options.offline:
194 WalletSynchronizer(wallet).start()
198 # check if --from_addr not in wallet (for mktx/payto)
201 if options.from_addr:
202 from_addr = options.from_addr
203 if from_addr not in wallet.all_addresses():
206 # commands needing password
207 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
208 password = getpass.getpass('Password:') if wallet.use_encryption and not is_temporary else None
211 wallet.pw_decode( wallet.seed, password)
213 print "invalid password"
218 if wallet.import_key(keypair,password):
219 print "keypair imported"
226 if cmd2 not in known_commands:
227 print "type 'electrum help <command>' to see the help for a specific command"
228 print "type 'electrum --help' to see the list of options"
229 print "list of commands:", ', '.join(known_commands)
230 elif cmd2 == 'balance':
231 print "Display the balance of your wallet or of an address."
232 print "syntax: balance [<address>]"
233 elif cmd2 == 'contacts':
234 print "show your list of contacts"
235 elif cmd2 == 'payto':
236 print "payto <recipient> <amount> [label]"
237 print "create and broadcast a transaction."
238 print "<recipient> can be a bitcoin address or a label"
239 print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address"
240 elif cmd2== 'sendtx':
242 print "broadcast a transaction to the network. <tx> must be in hexadecimal"
243 elif cmd2 == 'password':
244 print "change your password"
245 elif cmd2 == 'addresses':
246 print "show your list of addresses."
247 print "options:\n -a: show all addresses, including change addresses\n-k: show private keys\n-b: show the balance of addresses"
248 elif cmd2 == 'history':
249 print "show the transaction history"
250 elif cmd2 == 'label':
251 print "assign a label to an item"
253 print "start the GUI"
255 print "create a signed transaction. password protected"
256 print "syntax: mktx <recipient> <amount> [label]"
257 print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address"
259 print "print the generation seed of your wallet."
260 elif cmd2 == 'deseed':
261 print "remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'"
262 elif cmd2 == 'reseed':
263 print "restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key."
265 print "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\""
268 from electrum import mnemonic
269 seed = wallet.pw_decode( wallet.seed, password)
270 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
272 elif cmd == 'deseed':
274 print "Error: This wallet has no seed"
275 elif wallet.use_encryption:
276 print "Error: This wallet is encrypted"
278 ns = wallet.path + '.seed'
279 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
280 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
282 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
285 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
289 print "Action canceled."
291 elif cmd == 'reseed':
293 print "This wallet already has a seed", wallet.seed
295 ns = wallet.path + '.seed'
301 print "seed file not found"
305 d = ast.literal_eval( data )
307 imported_keys = d.get('imported_keys',{})
309 print "error with seed file"
312 mpk = wallet.master_public_key
314 wallet.imported_keys = imported_keys
315 wallet.use_encryption = False
316 wallet.init_mpk(seed)
317 if mpk == wallet.master_public_key:
319 print "Done: " + wallet.path
321 print "error: master public key does not match"
323 elif cmd == 'validateaddress':
325 print wallet.is_valid(addr)
327 elif cmd == 'balance':
333 c, u = wallet.get_balance()
335 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
337 print Decimal( c ) / 100000000
340 c, u = wallet.get_addr_balance(addr)
342 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
344 print "%s %s" % (addr, str(Decimal(c)/100000000))
346 elif cmd in [ 'contacts']:
347 for addr in wallet.addressbook:
348 print addr, " ", wallet.labels.get(addr)
354 elif cmd in [ 'addresses']:
355 for addr in wallet.all_addresses():
356 if options.show_all or not wallet.is_change(addr):
357 label = wallet.labels.get(addr)
359 if wallet.is_change(addr): _type = "[change]"
360 if addr in wallet.imported_keys.keys(): _type = "[imported]"
361 if label is None: label = ''
362 if options.show_balance:
363 h = wallet.history.get(addr,[])
366 if item['is_input']: ni += 1
368 b = "%d %d %s"%(no, ni, str(Decimal(wallet.get_addr_balance(addr)[0])/100000000))
370 if options.show_keys:
371 addr += ':' + str(wallet.get_private_key_base58(addr, password))
372 print addr, b, _type, label
375 lines = wallet.get_tx_history()
382 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
384 print line['timestamp']
386 label = line.get('label')
387 if not label: label = line['tx_hash']
388 else: label = label + ' '*(64 - len(label) )
390 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
391 print "# balance: ", format_satoshis(b)
396 label = ' '.join(args[2:])
398 print "syntax: label <tx_hash> <text>"
400 wallet.labels[tx] = label
403 elif cmd in ['payto', 'mktx']:
404 if from_addr and is_temporary:
405 if from_addr.find(":") == -1:
406 keypair = from_addr + ":" + getpass.getpass('Private key:')
409 from_addr = keypair.split(':')[0]
410 if not wallet.import_key(keypair,password):
411 print "invalid key pair"
413 wallet.history[from_addr] = interface.retrieve_history(from_addr)
414 wallet.update_tx_history()
415 change_addr = from_addr
417 if options.change_addr:
418 change_addr = options.change_addr
420 for k, v in wallet.labels.items():
423 print "alias", to_address
425 if change_addr and v == change_addr:
428 tx = wallet.mktx( to_address, amount, label, password,
429 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
432 traceback.print_exc(file=sys.stdout)
435 if tx and cmd=='payto':
436 r, h = wallet.sendtx( tx )
442 wallet.imported_keys.pop(from_addr)
443 del(wallet.history[from_addr])
446 elif cmd == 'sendtx':
448 r, h = wallet.sendtx( tx )
451 elif cmd == 'password':
453 seed = wallet.pw_decode( wallet.seed, password)
457 new_password = getpass.getpass('New password:')
458 if new_password == getpass.getpass('Confirm new password:'):
459 wallet.update_password(seed, password, new_password)
461 print "error: mismatch"
463 elif cmd == 'signmessage':
464 address, message = args[1:3]
465 print wallet.sign_message(address, message, password)
467 elif cmd == 'verifymessage':
468 address, signature, message = args[1:4]
470 wallet.verify_message(address, signature, message)