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"
219 wallet.import_key(keypair,password)
221 print "keypair imported"
222 except BaseException, e:
223 print( 'Error:' + str(e) )
227 if cmd2 not in known_commands:
228 print "type 'electrum help <command>' to see the help for a specific command"
229 print "type 'electrum --help' to see the list of options"
230 print "list of commands:", ', '.join(known_commands)
231 elif cmd2 == 'balance':
232 print "Display the balance of your wallet or of an address."
233 print "syntax: balance [<address>]"
234 elif cmd2 == 'contacts':
235 print "show your list of contacts"
236 elif cmd2 == 'payto':
237 print "payto <recipient> <amount> [label]"
238 print "create and broadcast a transaction."
239 print "<recipient> can be a bitcoin address or a label"
240 print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address"
241 elif cmd2== 'sendtx':
243 print "broadcast a transaction to the network. <tx> must be in hexadecimal"
244 elif cmd2 == 'password':
245 print "change your password"
246 elif cmd2 == 'addresses':
247 print "show your list of addresses."
248 print "options:\n -a: show all addresses, including change addresses\n-k: show private keys\n-b: show the balance of addresses"
249 elif cmd2 == 'history':
250 print "show the transaction history"
251 elif cmd2 == 'label':
252 print "assign a label to an item"
254 print "start the GUI"
256 print "create a signed transaction. password protected"
257 print "syntax: mktx <recipient> <amount> [label]"
258 print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address"
260 print "print the generation seed of your wallet."
261 elif cmd2 == 'deseed':
262 print "remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'"
263 elif cmd2 == 'reseed':
264 print "restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key."
266 print "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\""
269 from electrum import mnemonic
270 seed = wallet.pw_decode( wallet.seed, password)
271 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
273 elif cmd == 'deseed':
275 print "Error: This wallet has no seed"
276 elif wallet.use_encryption:
277 print "Error: This wallet is encrypted"
279 ns = wallet.path + '.seed'
280 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
281 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
283 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
286 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
290 print "Action canceled."
292 elif cmd == 'reseed':
294 print "This wallet already has a seed", wallet.seed
296 ns = wallet.path + '.seed'
302 print "seed file not found"
306 d = ast.literal_eval( data )
308 imported_keys = d.get('imported_keys',{})
310 print "error with seed file"
313 mpk = wallet.master_public_key
315 wallet.imported_keys = imported_keys
316 wallet.use_encryption = False
317 wallet.init_mpk(seed)
318 if mpk == wallet.master_public_key:
320 print "Done: " + wallet.path
322 print "error: master public key does not match"
324 elif cmd == 'validateaddress':
326 print wallet.is_valid(addr)
328 elif cmd == 'balance':
334 c, u = wallet.get_balance()
336 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
338 print Decimal( c ) / 100000000
341 c, u = wallet.get_addr_balance(addr)
343 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
345 print "%s %s" % (addr, str(Decimal(c)/100000000))
347 elif cmd in [ 'contacts']:
348 for addr in wallet.addressbook:
349 print addr, " ", wallet.labels.get(addr)
355 elif cmd in [ 'addresses']:
356 for addr in wallet.all_addresses():
357 if options.show_all or not wallet.is_change(addr):
358 label = wallet.labels.get(addr)
360 if wallet.is_change(addr): _type = "[change]"
361 if addr in wallet.imported_keys.keys(): _type = "[imported]"
362 if label is None: label = ''
363 if options.show_balance:
364 h = wallet.history.get(addr,[])
367 if item['is_input']: ni += 1
369 b = "%d %d %s"%(no, ni, str(Decimal(wallet.get_addr_balance(addr)[0])/100000000))
371 if options.show_keys:
372 addr += ':' + str(wallet.get_private_key_base58(addr, password))
373 print addr, b, _type, label
376 lines = wallet.get_tx_history()
383 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
385 print line['timestamp']
387 label = line.get('label')
388 if not label: label = line['tx_hash']
389 else: label = label + ' '*(64 - len(label) )
391 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
392 print "# balance: ", format_satoshis(b)
397 label = ' '.join(args[2:])
399 print "syntax: label <tx_hash> <text>"
401 wallet.labels[tx] = label
404 elif cmd in ['payto', 'mktx']:
405 if from_addr and is_temporary:
406 if from_addr.find(":") == -1:
407 keypair = from_addr + ":" + getpass.getpass('Private key:')
410 from_addr = keypair.split(':')[0]
411 if not wallet.import_key(keypair,password):
412 print "invalid key pair"
414 wallet.history[from_addr] = interface.retrieve_history(from_addr)
415 wallet.update_tx_history()
416 change_addr = from_addr
418 if options.change_addr:
419 change_addr = options.change_addr
421 for k, v in wallet.labels.items():
424 print "alias", to_address
426 if change_addr and v == change_addr:
429 tx = wallet.mktx( to_address, amount, label, password,
430 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
433 traceback.print_exc(file=sys.stdout)
436 if tx and cmd=='payto':
437 r, h = wallet.sendtx( tx )
443 wallet.imported_keys.pop(from_addr)
444 del(wallet.history[from_addr])
447 elif cmd == 'sendtx':
449 r, h = wallet.sendtx( tx )
452 elif cmd == 'password':
454 seed = wallet.pw_decode( wallet.seed, password)
458 new_password = getpass.getpass('New password:')
459 if new_password == getpass.getpass('Confirm new password:'):
460 wallet.update_password(seed, password, new_password)
462 print "error: mismatch"
464 elif cmd == 'signmessage':
465 address, message = args[1:3]
466 print wallet.sign_message(address, message, password)
468 elif cmd == 'verifymessage':
469 address, signature, message = args[1:4]
471 wallet.verify_message(address, signature, message)