Merge branch 'master' of gitorious.org:electrum/electrum

This commit is contained in:
thomasv 2012-05-14 13:35:47 +02:00
commit 796c992702
6 changed files with 180 additions and 83 deletions

View File

@ -24,8 +24,8 @@ from decimal import Decimal
from electrum import Wallet, SecretToASecret, WalletSynchronizer, format_satoshis from electrum import Wallet, SecretToASecret, WalletSynchronizer, format_satoshis
known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'restore', 'payto', 'sendtx', 'password', 'addresses', 'history', 'label', 'mktx','seed','import','signmessage','verifymessage','eval'] known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'restore', 'payto', 'sendtx', 'password', 'addresses', 'history', 'label', 'mktx','seed','import','signmessage','verifymessage','eval','deseed','reseed']
offline_commands = ['password', 'mktx', 'history', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed'] offline_commands = ['password', 'mktx', 'history', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed']
protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ] protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
if __name__ == '__main__': if __name__ == '__main__':
@ -34,6 +34,7 @@ if __name__ == '__main__':
parser = OptionParser(usage=usage) parser = OptionParser(usage=usage)
parser.add_option("-g", "--gui", dest="gui", default="qt", help="gui") parser.add_option("-g", "--gui", dest="gui", default="qt", help="gui")
parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)") parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses") parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses") parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses") parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
@ -135,18 +136,20 @@ if __name__ == '__main__':
sys.exit(1) sys.exit(1)
wallet.seed = str(seed) wallet.seed = str(seed)
WalletSynchronizer(wallet).start()
print "recovering wallet..."
wallet.init_mpk( wallet.seed ) wallet.init_mpk( wallet.seed )
wallet.up_to_date_event.clear() if not options.offline:
wallet.up_to_date = False WalletSynchronizer(wallet).start()
wallet.update() print "recovering wallet..."
if wallet.is_found(): wallet.up_to_date_event.clear()
wallet.fill_addressbook() wallet.up_to_date = False
wallet.save() wallet.update()
print "recovery successful" if wallet.is_found():
else: print "recovery successful"
print "found no history for this wallet" else:
print "found no history for this wallet"
wallet.fill_addressbook()
wallet.save()
print "Wallet saved in '%s'"%options.wallet_path
else: else:
wallet.new_seed(None) wallet.new_seed(None)
wallet.init_mpk( wallet.seed ) wallet.init_mpk( wallet.seed )
@ -156,6 +159,7 @@ if __name__ == '__main__':
print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet." print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:" print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\"" print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
print "Wallet saved in '%s'"%options.wallet_path
# check syntax # check syntax
if cmd in ['payto', 'mktx']: if cmd in ['payto', 'mktx']:
@ -171,7 +175,7 @@ if __name__ == '__main__':
cmd = 'help' cmd = 'help'
# open session # open session
if cmd not in offline_commands: if cmd not in offline_commands and not options.offline:
WalletSynchronizer(wallet).start() WalletSynchronizer(wallet).start()
wallet.update() wallet.update()
wallet.save() wallet.save()
@ -237,6 +241,8 @@ if __name__ == '__main__':
print "options: --fee, --fromaddr, --changeaddr" print "options: --fee, --fromaddr, --changeaddr"
elif cmd2 == 'seed': elif cmd2 == 'seed':
print "show generation seed of your wallet. password protected." print "show generation seed of your wallet. password protected."
elif cmd2 == 'deseed':
print "remove the seed of your wallet."
elif cmd2 == 'eval': elif cmd2 == 'eval':
print "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"" print "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\""
@ -245,6 +251,47 @@ if __name__ == '__main__':
seed = wallet.pw_decode( wallet.seed, password) seed = wallet.pw_decode( wallet.seed, password)
print seed, '"'+' '.join(mnemonic.mn_encode(seed))+'"' print seed, '"'+' '.join(mnemonic.mn_encode(seed))+'"'
elif cmd == 'deseed':
if not wallet.seed:
print "Eooro: This wallet has no seed"
elif wallet.use_encryption:
print "Error: This wallet is encrypted"
else:
ns = options.wallet_path+'.seed'
print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(options.wallet_path,ns)
if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
f = open(ns,'w')
f.write(wallet.seed)
f.close()
wallet.seed = ''
wallet.save()
print "Done."
else:
print "Action canceled."
elif cmd == 'reseed':
if wallet.seed:
print "This wallet already has a seed"
else:
ns = options.wallet_path+'.seed'
try:
f = open(ns,'r')
seed = f.read()
f.close()
except:
print "seed file not found"
sys.exit()
mpk = wallet.master_public_key
wallet.seed = seed
wallet.use_encryption = False
wallet.init_mpk(seed)
if mpk == wallet.master_public_key:
wallet.save()
print "done"
else:
print "error: master public key does not match"
elif cmd == 'validateaddress': elif cmd == 'validateaddress':
addr = args[1] addr = args[1]
print wallet.is_valid(addr) print wallet.is_valid(addr)

View File

@ -60,6 +60,9 @@ def numbify(entry, is_int = False):
def show_seed_dialog(wallet, password, parent): def show_seed_dialog(wallet, password, parent):
from electrum import mnemonic from electrum import mnemonic
if not wallet.seed:
show_message("No seed")
return
try: try:
seed = wallet.pw_decode( wallet.seed, password) seed = wallet.pw_decode( wallet.seed, password)
except: except:
@ -483,6 +486,10 @@ def password_dialog(parent):
if result != gtk.RESPONSE_CANCEL: return pw if result != gtk.RESPONSE_CANCEL: return pw
def change_password_dialog(wallet, parent, icon): def change_password_dialog(wallet, parent, icon):
if not wallet.seed:
show_message("No seed")
return
if parent: if parent:
msg = 'Your wallet is encrypted. Use this dialog to change the password. To disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted' msg = 'Your wallet is encrypted. Use this dialog to change the password. To disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted'
else: else:
@ -556,7 +563,9 @@ class ElectrumWindow:
self.funds_error = False # True if not enough funds self.funds_error = False # True if not enough funds
self.window = MyWindow(gtk.WINDOW_TOPLEVEL) self.window = MyWindow(gtk.WINDOW_TOPLEVEL)
self.window.set_title(APP_NAME + " " + self.wallet.electrum_version) title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.path
if not self.wallet.seed: title += ' [seedless]'
self.window.set_title(title)
self.window.connect("destroy", gtk.main_quit) self.window.connect("destroy", gtk.main_quit)
self.window.set_border_width(0) self.window.set_border_width(0)
self.window.connect('mykeypress', gtk.main_quit) self.window.connect('mykeypress', gtk.main_quit)
@ -566,7 +575,8 @@ class ElectrumWindow:
self.notebook = gtk.Notebook() self.notebook = gtk.Notebook()
self.create_history_tab() self.create_history_tab()
self.create_send_tab() if self.wallet.seed:
self.create_send_tab()
self.create_recv_tab() self.create_recv_tab()
self.create_book_tab() self.create_book_tab()
self.create_about_tab() self.create_about_tab()
@ -588,17 +598,18 @@ class ElectrumWindow:
self.network_button.show() self.network_button.show()
self.status_bar.pack_end(self.network_button, False, False) self.status_bar.pack_end(self.network_button, False, False)
def seedb(w, wallet): if self.wallet.seed:
if wallet.use_encryption: def seedb(w, wallet):
password = password_dialog(self.window) if wallet.use_encryption:
if not password: return password = password_dialog(self.window)
else: password = None if not password: return
show_seed_dialog(wallet, password, self.window) else: password = None
button = gtk.Button('S') show_seed_dialog(wallet, password, self.window)
button.connect("clicked", seedb, wallet ) button = gtk.Button('S')
button.set_relief(gtk.RELIEF_NONE) button.connect("clicked", seedb, wallet )
button.show() button.set_relief(gtk.RELIEF_NONE)
self.status_bar.pack_end(button,False, False) button.show()
self.status_bar.pack_end(button,False, False)
settings_icon = gtk.Image() settings_icon = gtk.Image()
settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
@ -620,12 +631,13 @@ class ElectrumWindow:
pw_icon.set_size_request(16,16 ) pw_icon.set_size_request(16,16 )
pw_icon.show() pw_icon.show()
password_button = gtk.Button() if self.wallet.seed:
password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon)) password_button = gtk.Button()
password_button.add(pw_icon) password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon))
password_button.set_relief(gtk.RELIEF_NONE) password_button.add(pw_icon)
password_button.show() password_button.set_relief(gtk.RELIEF_NONE)
self.status_bar.pack_end(password_button,False,False) password_button.show()
self.status_bar.pack_end(password_button,False,False)
self.window.add(vbox) self.window.add(vbox)
self.window.show_all() self.window.show_all()
@ -661,7 +673,8 @@ class ElectrumWindow:
thread.start_new_thread(update_status_bar_thread, ()) thread.start_new_thread(update_status_bar_thread, ())
thread.start_new_thread(check_recipient_thread, ()) if self.wallet.seed:
thread.start_new_thread(check_recipient_thread, ())
self.notebook.set_current_page(0) self.notebook.set_current_page(0)

View File

@ -157,7 +157,8 @@ class ElectrumWindow(QMainWindow):
self.tabs = tabs = QTabWidget(self) self.tabs = tabs = QTabWidget(self)
tabs.addTab(self.create_history_tab(), 'History') tabs.addTab(self.create_history_tab(), 'History')
tabs.addTab(self.create_send_tab(), 'Send') if self.wallet.seed:
tabs.addTab(self.create_send_tab(), 'Send')
tabs.addTab(self.create_receive_tab(), 'Receive') tabs.addTab(self.create_receive_tab(), 'Receive')
tabs.addTab(self.create_contacts_tab(),'Contacts') tabs.addTab(self.create_contacts_tab(),'Contacts')
tabs.addTab(self.create_wall_tab(), 'Wall') tabs.addTab(self.create_wall_tab(), 'Wall')
@ -166,7 +167,9 @@ class ElectrumWindow(QMainWindow):
self.setCentralWidget(tabs) self.setCentralWidget(tabs)
self.create_status_bar() self.create_status_bar()
self.setGeometry(100,100,840,400) self.setGeometry(100,100,840,400)
self.setWindowTitle( 'Electrum ' + self.wallet.electrum_version ) title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.path
if not self.wallet.seed: title += ' [seedless]'
self.setWindowTitle( title )
self.show() self.show()
QShortcut(QKeySequence("Ctrl+W"), self, self.close) QShortcut(QKeySequence("Ctrl+W"), self, self.close)
@ -178,8 +181,9 @@ class ElectrumWindow(QMainWindow):
def connect_slots(self, sender): def connect_slots(self, sender):
self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient) if self.wallet.seed:
self.previous_payto_e='' self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
self.previous_payto_e=''
def check_recipient(self): def check_recipient(self):
if self.payto_e.hasFocus(): if self.payto_e.hasFocus():
@ -512,43 +516,63 @@ class ElectrumWindow(QMainWindow):
entry.setPalette(palette) entry.setPalette(palette)
def get_current_addr(self, is_recv):
def clear_buttons(self, hbox):
while hbox.count(): hbox.removeItem(hbox.itemAt(0))
def add_buttons(self, l, hbox, is_recv):
self.clear_buttons(hbox)
i = l.currentItem()
if not i: return
addr = unicode( i.text(0) )
hbox.addWidget(EnterButton("QR",lambda: self.show_address_qrcode(addr)))
hbox.addWidget(EnterButton("Copy to Clipboard", lambda: self.app.clipboard().setText(addr)))
if is_recv: if is_recv:
def toggle_freeze(addr): l = self.receive_list
if addr in self.wallet.frozen_addresses:
self.wallet.frozen_addresses.remove(addr)
else:
self.wallet.frozen_addresses.append(addr)
self.wallet.save()
self.update_receive_tab()
t = "Unfreeze" if addr in self.wallet.frozen_addresses else "Freeze"
hbox.addWidget(EnterButton(t, lambda: toggle_freeze(addr)))
else: else:
def payto(addr): l = self.contacts_list
if not addr:return i = l.currentItem()
self.tabs.setCurrentIndex(1) if i:
self.payto_e.setText(addr) return unicode( i.text(0) )
self.amount_e.setFocus() else:
hbox.addWidget(EnterButton('Pay to', lambda: payto(addr))) return ''
hbox.addWidget(EnterButton("New", self.newaddress_dialog))
def add_receive_buttons(self):
l = self.receive_list
hbox = self.receive_buttons_hbox
hbox.addWidget(EnterButton("QR",lambda: self.show_address_qrcode(self.get_current_addr(True))))
hbox.addWidget(EnterButton("Copy to Clipboard", lambda: self.app.clipboard().setText(self.get_current_addr(True))))
def toggle_freeze():
addr = self.get_current_addr(True)
if not addr: return
if addr in self.wallet.frozen_addresses:
self.wallet.frozen_addresses.remove(addr)
else:
self.wallet.frozen_addresses.append(addr)
self.wallet.save()
self.update_receive_tab()
self.freezeButton = b = EnterButton("Freeze", toggle_freeze)
hbox.addWidget(b)
hbox.addStretch(1) hbox.addStretch(1)
def add_contacts_buttons(self):
l = self.contacts_list
hbox = self.contacts_buttons_hbox
hbox.addWidget(EnterButton("QR",lambda: self.show_address_qrcode(self.get_current_addr(False))))
hbox.addWidget(EnterButton("Copy to Clipboard", lambda: self.app.clipboard().setText(self.get_current_addr(False))))
def payto():
addr = self.get_current_addr(False)
if not addr:return
self.tabs.setCurrentIndex(1)
self.payto_e.setText(addr)
self.amount_e.setFocus()
hbox.addWidget(EnterButton('Pay to', lambda: payto()))
hbox.addWidget(EnterButton("New", self.newaddress_dialog))
hbox.addStretch(1)
def update_receive_buttons(self):
addr = self.get_current_addr(True)
t = "Unfreeze" if addr in self.wallet.frozen_addresses else "Freeze"
self.freezeButton.setText(t)
def create_receive_tab(self): def create_receive_tab(self):
l = QTreeWidget(self) l = QTreeWidget(self)
l.setColumnCount(4) l.setColumnCount(4)
@ -573,11 +597,15 @@ class ElectrumWindow(QMainWindow):
hbox.setSpacing(0) hbox.setSpacing(0)
buttons.setLayout(hbox) buttons.setLayout(hbox)
self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l)) self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l))
self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l)) self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l))
self.connect(l, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda: self.add_buttons(l, hbox, True)) l.selectionModel().currentChanged.connect(self.update_receive_buttons)
self.receive_list = l self.receive_list = l
self.receive_buttons_hbox = hbox self.receive_buttons_hbox = hbox
self.add_receive_buttons()
return w return w
def create_contacts_tab(self): def create_contacts_tab(self):
@ -606,16 +634,13 @@ class ElectrumWindow(QMainWindow):
self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l)) self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l))
self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l)) self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l))
self.connect(l, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.show_contact_details) self.connect(l, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.show_contact_details)
self.connect(l, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda: self.add_buttons(l, hbox, False))
self.contacts_list = l self.contacts_list = l
self.contacts_buttons_hbox = hbox self.contacts_buttons_hbox = hbox
self.add_contacts_buttons()
return w return w
def update_receive_tab(self): def update_receive_tab(self):
self.receive_list.clear() self.receive_list.clear()
self.clear_buttons(self.receive_buttons_hbox)
for address in self.wallet.all_addresses(): for address in self.wallet.all_addresses():
if self.wallet.is_change(address):continue if self.wallet.is_change(address):continue
label = self.wallet.labels.get(address,'') label = self.wallet.labels.get(address,'')
@ -647,7 +672,6 @@ class ElectrumWindow(QMainWindow):
def update_contacts_tab(self): def update_contacts_tab(self):
self.contacts_list.clear() self.contacts_list.clear()
self.clear_buttons(self.contacts_buttons_hbox)
for alias, v in self.wallet.aliases.items(): for alias, v in self.wallet.aliases.items():
s, target = v s, target = v
@ -674,9 +698,11 @@ class ElectrumWindow(QMainWindow):
def create_status_bar(self): def create_status_bar(self):
sb = QStatusBar() sb = QStatusBar()
sb.setFixedHeight(35) sb.setFixedHeight(35)
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) ) if self.wallet.seed:
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) ) sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) ) if self.wallet.seed:
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) ) self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
sb.addPermanentWidget( self.status_button ) sb.addPermanentWidget( self.status_button )
self.setStatusBar(sb) self.setStatusBar(sb)
@ -695,6 +721,11 @@ class ElectrumWindow(QMainWindow):
@staticmethod @staticmethod
def show_seed_dialog(wallet, parent=None): def show_seed_dialog(wallet, parent=None):
from electrum import mnemonic from electrum import mnemonic
if not wallet.seed:
QMessageBox.information(parent, 'Message', 'No seed', 'OK')
return
if wallet.use_encryption: if wallet.use_encryption:
password = parent.password_dialog() password = parent.password_dialog()
if not password: return if not password: return
@ -852,6 +883,11 @@ class ElectrumWindow(QMainWindow):
@staticmethod @staticmethod
def change_password_dialog( wallet, parent=None ): def change_password_dialog( wallet, parent=None ):
if not wallet.seed:
QMessageBox.information(parent, 'Message', 'No seed', 'OK')
return
d = QDialog(parent) d = QDialog(parent)
d.setModal(1) d.setModal(1)

View File

@ -218,9 +218,10 @@ class TcpStratumInterface(Interface):
self.s.connect(( self.host, self.port)) self.s.connect(( self.host, self.port))
self.is_connected = True self.is_connected = True
self.send([('server.version', [ELECTRUM_VERSION])]) self.send([('server.version', [ELECTRUM_VERSION])])
print "Connected to %s:%d"%(self.host,self.port)
except: except:
self.is_connected = False self.is_connected = False
print "not connected" print "Not connected"
def run(self): def run(self):
try: try:
@ -380,7 +381,6 @@ class WalletSynchronizer(threading.Thread):
response = self.interface.responses.get() response = self.interface.responses.get()
self.handle_response(response) self.handle_response(response)
print "disconnected, gui callback"
self.wallet.gui_callback() self.wallet.gui_callback()
if self.loop: if self.loop:
time.sleep(5) time.sleep(5)

View File

@ -1,2 +1,2 @@
ELECTRUM_VERSION = "0.48a" ELECTRUM_VERSION = "0.49"
SEED_VERSION = 4 # bump this everytime the seed generation is modified SEED_VERSION = 4 # bump this everytime the seed generation is modified

View File

@ -23,13 +23,13 @@ try:
import ecdsa import ecdsa
from ecdsa.util import string_to_number, number_to_string from ecdsa.util import string_to_number, number_to_string
except: except:
print "python-ecdsa does not seem to be installed. Try 'sudo easy_install ecdsa'" print "python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'"
sys.exit(1) sys.exit(1)
try: try:
import aes import aes
except: except:
print "AES does not seem to be installed. Try 'sudo easy_install slowaes'" print "AES does not seem to be installed. Try 'sudo pip install slowaes'"
sys.exit(1) sys.exit(1)
@ -308,6 +308,7 @@ class Wallet:
self.server = server self.server = server
self.save() self.save()
self.interface.is_connected = False # this exits the polling loop self.interface.is_connected = False # this exits the polling loop
self.interface.poke()
def set_path(self, wallet_path): def set_path(self, wallet_path):