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/>.
21 from lib.util import print_error
23 from electrum.util import print_error
28 print_error("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
34 print_error("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
38 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
40 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
42 from optparse import OptionParser
43 from decimal import Decimal
46 'help':'Prints this help',
47 'validateaddress':'Check that the address is valid',
48 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
49 'contacts': "Show your list of contacts",
50 'create':'Create a wallet',
51 'restore':'Restore a wallet',
52 'payto':"""Create and broadcast a transaction.
53 Syntax: payto <recipient> <amount> [label]
54 <recipient> can be a bitcoin address or a label
55 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
58 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
60 "Changes your password",
62 """Shows your list of addresses.
64 -a: show all addresses, including change addresses
66 -b: show the balance of addresses""",
68 'history':"Shows the transaction history",
69 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
71 """Create a signed transaction, password protected.
72 Syntax: mktx <recipient> <amount> [label]
73 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
76 "Print the generation seed of your wallet.",
78 'Imports a key pair\nSyntax: import <address>:<privatekey>',
80 'Signs a message with a key\nSyntax: signmessage <address> <message>',
82 'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>',
84 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
86 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
88 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
97 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
99 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
101 if __name__ == '__main__':
103 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
104 parser = OptionParser(usage=usage)
105 parser.add_option("-g", "--gui", dest="gui", default="lite", help="gui")
106 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
107 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
108 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
109 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
110 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
111 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
112 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.")
113 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")
114 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
115 options, args = parser.parse_args()
118 wallet.set_path(options.wallet_path)
120 wallet.remote_url = options.remote_url
125 elif len(args)==1 and re.match('^bitcoin:', args[0]):
130 firstarg = args[1] if len(args) > 1 else ''
133 if options.gui=='gtk':
135 import lib.gui as gui
137 import electrum.gui as gui
138 elif options.gui=='qt':
140 import lib.gui_qt as gui
142 import electrum.gui_qt as gui
143 elif options.gui == 'lite':
145 import lib.gui_lite as gui
147 import electrum.gui_lite as gui
149 print_error("Error: Unknown GUI: " + options.gui)
152 gui = gui.ElectrumGui(wallet)
153 WalletSynchronizer(wallet,True).start()
156 found = wallet.file_exists
158 found = gui.restore_or_create()
159 except SystemExit, e:
161 except BaseException, e:
163 traceback.print_exc(file=sys.stdout)
164 #gui.show_message(e.message)
173 if cmd not in known_commands:
176 if not wallet.file_exists and cmd not in ['help','create','restore']:
177 print_error("Error: Wallet file not found.")
178 print_error("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
181 if cmd in ['create', 'restore']:
182 if wallet.file_exists:
183 print_error("Error: Remove the existing wallet first!")
186 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
188 w_host, w_port, w_protocol = wallet.server.split(':')
189 host = raw_input("server (default:%s):"%w_host)
190 port = raw_input("port (default:%s):"%w_port)
191 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
192 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
193 gap = raw_input("gap limit (default 5):")
194 if host: w_host = host
195 if port: w_port = port
196 if protocol: w_protocol = protocol
197 wallet.server = w_host + ':' + w_port + ':' +w_protocol
198 if fee: wallet.fee = float(fee)
199 if gap: wallet.gap_limit = int(gap)
202 seed = raw_input("seed:")
206 print_error("Warning: Not hex, trying decode.")
207 seed = mnemonic.mn_decode( seed.split(' ') )
209 print_error("Error: No seed")
212 wallet.seed = str(seed)
213 wallet.init_mpk( wallet.seed )
214 if not options.offline:
215 WalletSynchronizer(wallet).start()
216 print "Recovering wallet..."
217 wallet.up_to_date_event.clear()
218 wallet.up_to_date = False
220 if wallet.is_found():
221 print "Recovery successful"
223 print_error("Warning: Found no history for this wallet")
224 wallet.fill_addressbook()
226 print_error("Wallet saved in '" + wallet.path)
228 wallet.new_seed(None)
229 wallet.init_mpk( wallet.seed )
230 wallet.synchronize() # there is no wallet thread
232 print "Your wallet generation seed is: " + wallet.seed
233 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
234 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
235 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
236 print "Wallet saved in '%s'"%wallet.path
239 wallet.update_password(wallet.seed, None, password)
242 if cmd in ['payto', 'mktx']:
245 amount = int( 100000000 * Decimal(args[2]) )
247 label = ' '.join(args[3:])
249 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
255 if cmd not in offline_commands and not options.offline:
256 WalletSynchronizer(wallet).start()
260 # check if --from_addr not in wallet (for mktx/payto)
263 if options.from_addr:
264 from_addr = options.from_addr
265 if from_addr not in wallet.all_addresses():
268 # commands needing password
269 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
270 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
273 wallet.pw_decode( wallet.seed, password)
275 print_error("Error: This password does not decode this wallet.")
279 # See if they specificed a key on the cmd line, if not prompt
283 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
285 wallet.import_key(keypair,password)
287 print "Keypair imported"
288 except BaseException, e:
289 print_error("Error: Keypair import failed: " + str(e))
293 if cmd2 not in known_commands:
296 print "Type 'electrum help <command>' to see the help for a specific command"
297 print "Type 'electrum --help' to see the list of options"
298 print "List of commands:", ', '.join(known_commands)
300 print known_commands[cmd2]
303 seed = wallet.pw_decode( wallet.seed, password)
304 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
306 elif cmd == 'deseed':
308 print_error("Error: This wallet has no seed")
309 elif wallet.use_encryption:
310 print_error("Error: This wallet is encrypted")
312 ns = wallet.path + '.seed'
313 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
314 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
316 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
319 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
323 print_error("Action canceled.")
325 elif cmd == 'reseed':
327 print "Warning: This wallet already has a seed", wallet.seed
329 ns = wallet.path + '.seed'
335 print_error("Error: Seed file not found")
339 d = ast.literal_eval( data )
341 imported_keys = d.get('imported_keys',{})
343 print_error("Error: Error with seed file")
346 mpk = wallet.master_public_key
348 wallet.imported_keys = imported_keys
349 wallet.use_encryption = False
350 wallet.init_mpk(seed)
351 if mpk == wallet.master_public_key:
353 print "Done: " + wallet.path
355 print_error("Error: Master public key does not match")
357 elif cmd == 'validateaddress':
359 print wallet.is_valid(addr)
361 elif cmd == 'balance':
367 c, u = wallet.get_balance()
369 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
371 print Decimal( c ) / 100000000
374 c, u = wallet.get_addr_balance(addr)
376 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
378 print "%s %s" % (addr, str(Decimal(c)/100000000))
380 elif cmd in [ 'contacts']:
381 for addr in wallet.addressbook:
382 print addr, " ", wallet.labels.get(addr)
388 elif cmd in [ 'addresses']:
389 for addr in wallet.all_addresses():
390 if options.show_all or not wallet.is_change(addr):
392 flags = wallet.get_address_flags(addr)
393 label = wallet.labels.get(addr,'')
395 if label: label = "\"%s\""%label
397 if options.show_balance:
398 h = wallet.history.get(addr,[])
401 # if item['is_input']: ni += 1
403 b = format_satoshis(wallet.get_addr_balance(addr)[0])
406 if options.show_keys:
407 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
408 print flags, m_addr, b, label
411 lines = wallet.get_tx_history()
418 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
420 print line['timestamp']
422 label = line.get('label')
423 if not label: label = line['tx_hash']
424 else: label = label + ' '*(64 - len(label) )
426 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
427 print "# balance: ", format_satoshis(b)
432 label = ' '.join(args[2:])
434 print_error("Error. Syntax: label <tx_hash> <text>")
436 wallet.labels[tx] = label
439 elif cmd in ['payto', 'mktx']:
440 if from_addr and is_temporary:
441 if from_addr.find(":") == -1:
442 keypair = from_addr + ":" + prompt_password('Private key:', False)
445 from_addr = keypair.split(':')[0]
446 if not wallet.import_key(keypair,password):
447 print_error("Error: Invalid key pair")
449 wallet.history[from_addr] = interface.retrieve_history(from_addr)
450 wallet.update_tx_history()
451 change_addr = from_addr
453 if options.change_addr:
454 change_addr = options.change_addr
456 for k, v in wallet.labels.items():
459 print "alias", to_address
461 if change_addr and v == change_addr:
464 tx = wallet.mktx( to_address, amount, label, password,
465 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
468 traceback.print_exc(file=sys.stdout)
471 if tx and cmd=='payto':
472 r, h = wallet.sendtx( tx )
478 wallet.imported_keys.pop(from_addr)
479 del(wallet.history[from_addr])
482 elif cmd == 'sendtx':
484 r, h = wallet.sendtx( tx )
487 elif cmd == 'password':
489 seed = wallet.pw_decode( wallet.seed, password)
491 print_error("Error: Password does not decrypt this wallet.")
494 new_password = prompt_password('New password:')
495 wallet.update_password(seed, password, new_password)
497 elif cmd == 'signmessage':
499 print_error("Error: Invalid usage of signmessage.")
500 print known_commands[cmd]
503 message = ' '.join(args[2:])
505 print "Warning: Message was reconstructed from several arguments:", repr(message)
506 print wallet.sign_message(address, message, password)
508 elif cmd == 'verifymessage':
512 message = ' '.join(args[3:])
514 print_error("Error: Not all parameters were given, displaying help instead.")
515 print known_commands[cmd]
518 print "Warning: Message was reconstructed from several arguments:", repr(message)
520 wallet.verify_message(address, signature, message)
525 elif cmd == 'freeze':
527 print self.wallet.freeze(addr)
529 elif cmd == 'unfreeze':
531 print self.wallet.unfreeze(addr)
533 elif cmd == 'prioritize':
535 print self.wallet.prioritize(addr)
537 elif cmd == 'unprioritize':
539 print self.wallet.unprioritize(addr)