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/>.
20 from lib.util import print_error
25 print_error("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
31 print_error("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
35 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
37 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password
39 from optparse import OptionParser
40 from decimal import Decimal
43 'help':'Prints this help',
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 a wallet',
48 'restore':'Restore a wallet',
49 'payto':"""Create and broadcast a transaction.
50 Syntax: payto <recipient> <amount> [label]
51 <recipient> can be a bitcoin address or a label
52 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
55 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
57 "Changes your password",
59 """Shows your list of addresses.
61 -a: show all addresses, including change addresses
63 -b: show the balance of addresses""",
65 'history':"Shows the transaction history",
66 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
68 """Create a signed transaction, password protected.
69 Syntax: mktx <recipient> <amount> [label]
70 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
73 "Print the generation seed of your wallet.",
75 'Imports a key pair\nSyntax: import <address>:<privatekey>',
77 'Signs a message with a key\nSyntax: signmessage <address> <message>',
79 'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>',
81 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
83 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
85 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
94 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
96 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
98 if __name__ == '__main__':
100 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
101 parser = OptionParser(usage=usage)
102 parser.add_option("-g", "--gui", dest="gui", default="lite", help="gui")
103 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
104 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
105 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
106 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
107 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
108 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
109 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.")
110 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")
111 parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
112 options, args = parser.parse_args()
115 wallet.set_path(options.wallet_path)
117 wallet.remote_url = options.remote_url
122 elif len(args)==1 and re.match('^bitcoin:', args[0]):
127 firstarg = args[1] if len(args) > 1 else ''
130 if options.gui=='gtk':
132 import lib.gui as gui
134 import electrum.gui as gui
135 elif options.gui=='qt':
137 import lib.gui_qt as gui
139 import electrum.gui_qt as gui
140 elif options.gui == 'lite':
142 import lib.gui_lite as gui
144 import electrum.gui_lite as gui
146 print_error("Error: Unknown GUI: " + options.gui)
149 gui = gui.ElectrumGui(wallet)
150 WalletSynchronizer(wallet,True).start()
153 found = wallet.file_exists
155 found = gui.restore_or_create()
156 except SystemExit, e:
158 except BaseException, e:
160 traceback.print_exc(file=sys.stdout)
161 #gui.show_message(e.message)
170 if cmd not in known_commands:
173 if not wallet.file_exists and cmd not in ['help','create','restore']:
174 print_error("Error: Wallet file not found.")
175 print_error("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
178 if cmd in ['create', 'restore']:
179 if wallet.file_exists:
180 print_error("Error: Remove the existing wallet first!")
183 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
185 w_host, w_port, w_protocol = wallet.server.split(':')
186 host = raw_input("server (default:%s):"%w_host)
187 port = raw_input("port (default:%s):"%w_port)
188 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
189 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
190 gap = raw_input("gap limit (default 5):")
191 if host: w_host = host
192 if port: w_port = port
193 if protocol: w_protocol = protocol
194 wallet.server = w_host + ':' + w_port + ':' +w_protocol
195 if fee: wallet.fee = float(fee)
196 if gap: wallet.gap_limit = int(gap)
199 seed = raw_input("seed:")
203 print_error("Warning: Not hex, trying decode.")
204 seed = mnemonic.mn_decode( seed.split(' ') )
206 print_error("Error: No seed")
209 wallet.seed = str(seed)
210 wallet.init_mpk( wallet.seed )
211 if not options.offline:
212 WalletSynchronizer(wallet).start()
213 print "Recovering wallet..."
214 wallet.up_to_date_event.clear()
215 wallet.up_to_date = False
217 if wallet.is_found():
218 print "Recovery successful"
220 print_error("Warning: Found no history for this wallet")
221 wallet.fill_addressbook()
223 print_error("Wallet saved in '" + wallet.path)
225 wallet.new_seed(None)
226 wallet.init_mpk( wallet.seed )
227 wallet.synchronize() # there is no wallet thread
229 print "Your wallet generation seed is: " + wallet.seed
230 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
231 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
232 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
233 print "Wallet saved in '%s'"%wallet.path
236 wallet.update_password(wallet.seed, None, password)
239 if cmd in ['payto', 'mktx']:
242 amount = int( 100000000 * Decimal(args[2]) )
244 label = ' '.join(args[3:])
246 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
252 if cmd not in offline_commands and not options.offline:
253 WalletSynchronizer(wallet).start()
257 # check if --from_addr not in wallet (for mktx/payto)
260 if options.from_addr:
261 from_addr = options.from_addr
262 if from_addr not in wallet.all_addresses():
265 # commands needing password
266 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
267 password = prompt_password('Password:') if wallet.use_encryption and not is_temporary else None
270 wallet.pw_decode( wallet.seed, password)
272 print_error("Error: This password does not decode this wallet.")
278 wallet.import_key(keypair,password)
280 print "Keypair imported"
281 except BaseException, e:
282 print_error("Error: Keypair import failed: " + str(e))
286 if cmd2 not in known_commands:
287 print_error("Error: Command not found.")
288 print "Type 'electrum help <command>' to see the help for a specific command"
289 print "Type 'electrum --help' to see the list of options"
290 print "List of commands:", ', '.join(known_commands)
292 print known_commands[cmd2]
295 seed = wallet.pw_decode( wallet.seed, password)
296 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
298 elif cmd == 'deseed':
300 print_error("Error: This wallet has no seed")
301 elif wallet.use_encryption:
302 print_error("Error: This wallet is encrypted")
304 ns = wallet.path + '.seed'
305 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
306 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
308 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
311 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
315 print_error("Action canceled.")
317 elif cmd == 'reseed':
319 print "Warning: This wallet already has a seed", wallet.seed
321 ns = wallet.path + '.seed'
327 print_error("Error: Seed file not found")
331 d = ast.literal_eval( data )
333 imported_keys = d.get('imported_keys',{})
335 print_error("Error: Error with seed file")
338 mpk = wallet.master_public_key
340 wallet.imported_keys = imported_keys
341 wallet.use_encryption = False
342 wallet.init_mpk(seed)
343 if mpk == wallet.master_public_key:
345 print "Done: " + wallet.path
347 print_error("Error: Master public key does not match")
349 elif cmd == 'validateaddress':
351 print wallet.is_valid(addr)
353 elif cmd == 'balance':
359 c, u = wallet.get_balance()
361 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
363 print Decimal( c ) / 100000000
366 c, u = wallet.get_addr_balance(addr)
368 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
370 print "%s %s" % (addr, str(Decimal(c)/100000000))
372 elif cmd in [ 'contacts']:
373 for addr in wallet.addressbook:
374 print addr, " ", wallet.labels.get(addr)
380 elif cmd in [ 'addresses']:
381 for addr in wallet.all_addresses():
382 if options.show_all or not wallet.is_change(addr):
384 flags = wallet.get_address_flags(addr)
385 label = wallet.labels.get(addr,'')
387 if label: label = "\"%s\""%label
389 if options.show_balance:
390 h = wallet.history.get(addr,[])
393 # if item['is_input']: ni += 1
395 b = format_satoshis(wallet.get_addr_balance(addr)[0])
398 if options.show_keys:
399 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
400 print flags, m_addr, b, 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_error("Error. 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 + ":" + prompt_password('Private key:', False)
437 from_addr = keypair.split(':')[0]
438 if not wallet.import_key(keypair,password):
439 print_error("Error: 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)
483 print_error("Error: Password does not decrypt this wallet.")
486 new_password = prompt_password('New password:')
487 wallet.update_password(seed, password, new_password)
489 elif cmd == 'signmessage':
491 message = ' '.join(args[2:])
493 print "Warning: Message was reconstructed from several arguments:", repr(message)
494 print wallet.sign_message(address, message, password)
496 elif cmd == 'verifymessage':
500 message = ' '.join(args[3:])
502 print_error("Error: Not all parameters were given, displaying help instead.")
503 print known_commands[cmd]
506 print "Warning: Message was reconstructed from several arguments:", repr(message)
508 wallet.verify_message(address, signature, message)
513 elif cmd == 'freeze':
515 print self.wallet.freeze(addr)
517 elif cmd == 'unfreeze':
519 print self.wallet.unfreeze(addr)
521 elif cmd == 'prioritize':
523 print self.wallet.prioritize(addr)
525 elif cmd == 'unprioritize':
527 print self.wallet.unprioritize(addr)