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/>.
24 from lib.util import print_error
26 from electrum.util import print_error
31 sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
36 sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
39 from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, SimpleConfig, pick_random_server
41 from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, SimpleConfig, pick_random_server
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>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello This is a weird String "',
82 'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello This is a weird String "',
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',
99 'help', 'validateaddress',
100 'signmessage', 'verifymessage',
101 'eval', 'create', 'addresses',
105 'prioritize','unprioritize']
108 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
110 if __name__ == '__main__':
112 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
113 parser = optparse.OptionParser(prog=usage)
114 parser.add_option("-g", "--gui", dest="gui", help="gui")
115 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
116 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
117 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
118 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
119 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
120 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
121 parser.add_option("-F", "--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.")
122 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")
123 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
124 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
125 options, args = parser.parse_args()
127 # config is an object passed to the various constructors (wallet, interface, gui)
128 config = SimpleConfig(options)
129 wallet = Wallet(config)
134 elif len(args)==1 and re.match('^bitcoin:', args[0]):
139 firstarg = args[1] if len(args) > 1 else ''
141 #this entire if/else block is just concerned with importing the
142 #right GUI toolkit based the GUI command line option given
144 pref_gui = config.get('gui','qt')
145 if pref_gui == 'gtk':
147 import lib.gui as gui
149 import electrum.gui as gui
150 elif pref_gui == 'qt':
152 import lib.gui_qt as gui
154 import electrum.gui_qt as gui
155 elif pref_gui == 'lite':
157 import lib.gui_lite as gui
159 import electrum.gui_lite as gui
161 sys.exit("Error: Unknown GUI: " + pref_gui )
163 gui = gui.ElectrumGui(wallet, config)
164 interface = WalletSynchronizer(wallet, config, True, gui.server_list_changed)
168 found = config.wallet_file_exists
170 found = gui.restore_or_create()
171 except SystemExit, e:
173 except BaseException, e:
175 traceback.print_exc(file=sys.stdout)
176 #gui.show_message(e.message)
185 if cmd not in known_commands:
188 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
189 print "Error: Wallet file not found."
190 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
193 if cmd in ['create', 'restore']:
194 if config.wallet_file_exists:
195 sys.exit("Error: Remove the existing wallet first!")
196 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
198 server = config.get('server')
199 if not server: server = pick_random_server()
200 w_host, w_port, w_protocol = server.split(':')
201 host = raw_input("server (default:%s):"%w_host)
202 port = raw_input("port (default:%s):"%w_port)
203 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
204 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
205 gap = raw_input("gap limit (default 5):")
206 if host: w_host = host
207 if port: w_port = port
208 if protocol: w_protocol = protocol
209 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
210 if fee: wallet.fee = float(fee)
211 if gap: wallet.gap_limit = int(gap)
214 seed = raw_input("seed:")
218 print_error("Warning: Not hex, trying decode.")
219 seed = mnemonic.mn_decode( seed.split(' ') )
221 sys.exit("Error: No seed")
223 wallet.seed = str(seed)
224 wallet.init_mpk( wallet.seed )
225 if not options.offline:
226 WalletSynchronizer(wallet, config).start()
227 print "Recovering wallet..."
228 wallet.up_to_date_event.clear()
229 wallet.up_to_date = False
231 if wallet.is_found():
232 print "Recovery successful"
234 print_error("Warning: Found no history for this wallet")
237 wallet.fill_addressbook()
239 print_error("Wallet saved in '" + wallet.path)
241 wallet.new_seed(None)
242 wallet.init_mpk( wallet.seed )
243 wallet.synchronize() # there is no wallet thread
245 print "Your wallet generation seed is: " + wallet.seed
246 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
247 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
248 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
249 print "Wallet saved in '%s'"%wallet.config.path
252 wallet.update_password(wallet.seed, None, password)
255 if cmd in ['payto', 'mktx']:
258 amount = int( 100000000 * Decimal(args[2]) )
260 label = ' '.join(args[3:])
262 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
268 if cmd not in offline_commands and not options.offline:
269 WalletSynchronizer(wallet, config).start()
273 # check if --from_addr not in wallet (for mktx/payto)
276 if options.from_addr:
277 from_addr = options.from_addr
278 if from_addr not in wallet.all_addresses():
282 if cmd=='addresses' and options.show_keys:
283 print "WARNING: ALL your private keys are secret."
284 print "Exposing a single private key can compromise your entire wallet!"
285 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
287 # commands needing password
288 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
289 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
292 wallet.pw_decode( wallet.seed, password)
294 print_error("Error: This password does not decode this wallet.")
298 # See if they specificed a key on the cmd line, if not prompt
302 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
304 wallet.import_key(keypair,password)
306 print "Keypair imported"
307 except BaseException, e:
308 print_error("Error: Keypair import failed: " + str(e))
312 if cmd2 not in known_commands:
314 print "Type 'electrum help <command>' to see the help for a specific command"
315 print "Type 'electrum --help' to see the list of options"
316 print "List of commands:", ', '.join(known_commands)
318 print known_commands[cmd2]
321 seed = wallet.pw_decode( wallet.seed, password)
322 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
324 elif cmd == 'deseed':
326 print_error("Error: This wallet has no seed")
327 elif wallet.use_encryption:
328 print_error("Error: This wallet is encrypted")
330 ns = wallet.path + '.seed'
331 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
332 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
334 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
337 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
341 print_error("Action canceled.")
343 elif cmd == 'reseed':
345 print "Warning: This wallet already has a seed", wallet.seed
347 ns = wallet.path + '.seed'
353 sys.exit("Error: Seed file not found")
356 d = ast.literal_eval( data )
358 imported_keys = d.get('imported_keys',{})
360 sys.exit("Error: Error with seed file")
362 mpk = wallet.master_public_key
364 wallet.imported_keys = imported_keys
365 wallet.use_encryption = False
366 wallet.init_mpk(seed)
367 if mpk == wallet.master_public_key:
369 print "Done: " + wallet.path
371 print_error("Error: Master public key does not match")
373 elif cmd == 'validateaddress':
375 print wallet.is_valid(addr)
377 elif cmd == 'balance':
383 c, u = wallet.get_balance()
385 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
387 print Decimal( c ) / 100000000
390 c, u = wallet.get_addr_balance(addr)
392 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
394 print "%s %s" % (addr, str(Decimal(c)/100000000))
396 elif cmd in [ 'contacts']:
397 for addr in wallet.addressbook:
398 print addr, " ", wallet.labels.get(addr)
404 elif cmd in [ 'addresses']:
405 for addr in wallet.all_addresses():
406 if options.show_all or not wallet.is_change(addr):
408 flags = wallet.get_address_flags(addr)
409 label = wallet.labels.get(addr,'')
411 if label: label = "\"%s\""%label
413 if options.show_balance:
414 h = wallet.history.get(addr,[])
417 # if item['is_input']: ni += 1
419 b = format_satoshis(wallet.get_addr_balance(addr)[0])
422 if options.show_keys:
423 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
424 print flags, m_addr, b, label
427 lines = wallet.get_tx_history()
434 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
436 print line['timestamp']
438 label = line.get('label')
439 if not label: label = line['tx_hash']
440 else: label = label + ' '*(64 - len(label) )
442 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
443 print "# balance: ", format_satoshis(b)
448 label = ' '.join(args[2:])
450 print_error("Error. Syntax: label <tx_hash> <text>")
452 wallet.labels[tx] = label
455 elif cmd in ['payto', 'mktx']:
456 if from_addr and is_temporary:
457 if from_addr.find(":") == -1:
458 keypair = from_addr + ":" + prompt_password('Private key:', False)
461 from_addr = keypair.split(':')[0]
462 if not wallet.import_key(keypair,password):
463 print_error("Error: Invalid key pair")
465 wallet.history[from_addr] = interface.retrieve_history(from_addr)
466 wallet.update_tx_history()
467 change_addr = from_addr
469 if options.change_addr:
470 change_addr = options.change_addr
472 for k, v in wallet.labels.items():
475 print "alias", to_address
477 if change_addr and v == change_addr:
480 tx = wallet.mktx( to_address, amount, label, password,
481 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
484 traceback.print_exc(file=sys.stdout)
487 if tx and cmd=='payto':
488 r, h = wallet.sendtx( tx )
494 wallet.imported_keys.pop(from_addr)
495 del(wallet.history[from_addr])
498 elif cmd == 'sendtx':
500 r, h = wallet.sendtx( tx )
503 elif cmd == 'password':
505 seed = wallet.pw_decode( wallet.seed, password)
507 sys.exit("Error: Password does not decrypt this wallet.")
509 new_password = prompt_password('New password:')
510 wallet.update_password(seed, password, new_password)
512 elif cmd == 'signmessage':
514 print_error("Error: Invalid usage of signmessage.")
515 print known_commands[cmd]
518 message = ' '.join(args[2:])
520 print "Warning: Message was reconstructed from several arguments:", repr(message)
521 print wallet.sign_message(address, message, password)
523 elif cmd == 'verifymessage':
527 message = ' '.join(args[3:])
529 print_error("Error: Not all parameters were given, displaying help instead.")
530 print known_commands[cmd]
533 print "Warning: Message was reconstructed from several arguments:", repr(message)
535 wallet.verify_message(address, signature, message)
537 except BaseException as e:
538 print "Verification error: {0}".format(e)
541 elif cmd == 'freeze':
543 print self.wallet.freeze(addr)
545 elif cmd == 'unfreeze':
547 print self.wallet.unfreeze(addr)
549 elif cmd == 'prioritize':
551 print self.wallet.prioritize(addr)
553 elif cmd == 'unprioritize':
555 print self.wallet.unprioritize(addr)