Disentangle plugins and window management; use Qt signals
This commit is contained in:
parent
b727824eed
commit
175fdbcac6
|
@ -91,6 +91,7 @@ class ElectrumGui:
|
||||||
self.build_tray_menu()
|
self.build_tray_menu()
|
||||||
self.tray.show()
|
self.tray.show()
|
||||||
self.app.connect(self.app, QtCore.SIGNAL('new_window'), self.start_new_window)
|
self.app.connect(self.app, QtCore.SIGNAL('new_window'), self.start_new_window)
|
||||||
|
run_hook('init_qt', self)
|
||||||
|
|
||||||
def build_tray_menu(self):
|
def build_tray_menu(self):
|
||||||
# Avoid immediate GC of old menu when window closed via its action
|
# Avoid immediate GC of old menu when window closed via its action
|
||||||
|
@ -217,7 +218,7 @@ class ElectrumGui:
|
||||||
w.show()
|
w.show()
|
||||||
self.windows.append(w)
|
self.windows.append(w)
|
||||||
self.build_tray_menu()
|
self.build_tray_menu()
|
||||||
self.plugins.on_new_window(w)
|
run_hook('on_new_window', w)
|
||||||
|
|
||||||
if uri:
|
if uri:
|
||||||
w.pay_to_URI(uri)
|
w.pay_to_URI(uri)
|
||||||
|
@ -227,7 +228,7 @@ class ElectrumGui:
|
||||||
def close_window(self, window):
|
def close_window(self, window):
|
||||||
self.windows.remove(window)
|
self.windows.remove(window)
|
||||||
self.build_tray_menu()
|
self.build_tray_menu()
|
||||||
self.plugins.on_close_window(window)
|
run_hook('on_close_window', window)
|
||||||
|
|
||||||
def main(self):
|
def main(self):
|
||||||
self.timer.start()
|
self.timer.start()
|
||||||
|
|
|
@ -565,8 +565,6 @@ class ElectrumWindow(QMainWindow, PrintError):
|
||||||
|
|
||||||
def update_wallet(self):
|
def update_wallet(self):
|
||||||
self.update_status()
|
self.update_status()
|
||||||
if self.wallet is None:
|
|
||||||
return
|
|
||||||
if self.wallet.up_to_date or not self.network or not self.network.is_connected():
|
if self.wallet.up_to_date or not self.network or not self.network.is_connected():
|
||||||
self.update_tabs()
|
self.update_tabs()
|
||||||
|
|
||||||
|
@ -2868,6 +2866,7 @@ class ElectrumWindow(QMainWindow, PrintError):
|
||||||
p = plugins.toggle_enabled(self.config, name)
|
p = plugins.toggle_enabled(self.config, name)
|
||||||
cb.setChecked(bool(p))
|
cb.setChecked(bool(p))
|
||||||
enable_settings_widget(p, name, i)
|
enable_settings_widget(p, name, i)
|
||||||
|
run_hook('init_qt', self.gui_object)
|
||||||
|
|
||||||
for i, descr in enumerate(plugins.descriptions):
|
for i, descr in enumerate(plugins.descriptions):
|
||||||
name = descr['name']
|
name = descr['name']
|
||||||
|
|
|
@ -39,7 +39,6 @@ class Plugins(PrintError):
|
||||||
self.pathname = None
|
self.pathname = None
|
||||||
|
|
||||||
self.plugins = {}
|
self.plugins = {}
|
||||||
self.windows = []
|
|
||||||
self.network = None
|
self.network = None
|
||||||
self.descriptions = plugins.descriptions
|
self.descriptions = plugins.descriptions
|
||||||
for item in self.descriptions:
|
for item in self.descriptions:
|
||||||
|
@ -52,6 +51,7 @@ class Plugins(PrintError):
|
||||||
if config.get('use_' + name):
|
if config.get('use_' + name):
|
||||||
self.load_plugin(config, name)
|
self.load_plugin(config, name)
|
||||||
|
|
||||||
|
|
||||||
def get(self, name):
|
def get(self, name):
|
||||||
return self.plugins.get(name)
|
return self.plugins.get(name)
|
||||||
|
|
||||||
|
@ -67,9 +67,6 @@ class Plugins(PrintError):
|
||||||
else:
|
else:
|
||||||
p = __import__(full_name, fromlist=['electrum_plugins'])
|
p = __import__(full_name, fromlist=['electrum_plugins'])
|
||||||
plugin = p.Plugin(self, config, name)
|
plugin = p.Plugin(self, config, name)
|
||||||
# Inform the plugin of our windows
|
|
||||||
for window in self.windows:
|
|
||||||
plugin.on_new_window(window)
|
|
||||||
if self.network:
|
if self.network:
|
||||||
self.network.add_jobs(plugin.thread_jobs())
|
self.network.add_jobs(plugin.thread_jobs())
|
||||||
self.plugins[name] = plugin
|
self.plugins[name] = plugin
|
||||||
|
@ -80,6 +77,7 @@ class Plugins(PrintError):
|
||||||
traceback.print_exc(file=sys.stdout)
|
traceback.print_exc(file=sys.stdout)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def close_plugin(self, plugin):
|
def close_plugin(self, plugin):
|
||||||
if self.network:
|
if self.network:
|
||||||
self.network.remove_jobs(plugin.thread_jobs())
|
self.network.remove_jobs(plugin.thread_jobs())
|
||||||
|
@ -132,17 +130,6 @@ class Plugins(PrintError):
|
||||||
if network:
|
if network:
|
||||||
network.add_jobs(jobs)
|
network.add_jobs(jobs)
|
||||||
|
|
||||||
def trigger(self, event, *args, **kwargs):
|
|
||||||
for plugin in self.plugins.values():
|
|
||||||
getattr(plugin, event)(*args, **kwargs)
|
|
||||||
|
|
||||||
def on_new_window(self, window):
|
|
||||||
self.windows.append(window)
|
|
||||||
self.trigger('on_new_window', window)
|
|
||||||
|
|
||||||
def on_close_window(self, window):
|
|
||||||
self.windows.remove(window)
|
|
||||||
self.trigger('on_close_window', window)
|
|
||||||
|
|
||||||
|
|
||||||
hook_names = set()
|
hook_names = set()
|
||||||
|
@ -226,9 +213,3 @@ class BasePlugin(PrintError):
|
||||||
def settings_dialog(self):
|
def settings_dialog(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Events
|
|
||||||
def on_close_window(self, window):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_new_window(self, window):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -1046,11 +1046,6 @@ class Abstract_Wallet(PrintError):
|
||||||
# Sign
|
# Sign
|
||||||
if keypairs:
|
if keypairs:
|
||||||
tx.sign(keypairs)
|
tx.sign(keypairs)
|
||||||
# Run hook, and raise if error
|
|
||||||
tx.error = None
|
|
||||||
run_hook('sign_transaction', self, tx, password)
|
|
||||||
if tx.error:
|
|
||||||
raise BaseException(tx.error)
|
|
||||||
|
|
||||||
|
|
||||||
def sendtx(self, tx):
|
def sendtx(self, tx):
|
||||||
|
|
|
@ -89,43 +89,38 @@ class Plugin(BasePlugin):
|
||||||
self.keys = []
|
self.keys = []
|
||||||
self.cosigner_list = []
|
self.cosigner_list = []
|
||||||
|
|
||||||
|
@hook
|
||||||
def on_new_window(self, window):
|
def on_new_window(self, window):
|
||||||
self.update()
|
self.update(window)
|
||||||
|
|
||||||
|
@hook
|
||||||
def on_close_window(self, window):
|
def on_close_window(self, window):
|
||||||
self.update()
|
self.update(window)
|
||||||
|
|
||||||
def available_wallets(self):
|
|
||||||
result = {}
|
|
||||||
for window in self.parent.windows:
|
|
||||||
if window.wallet.wallet_type in ['2of2', '2of3']:
|
|
||||||
result[window.wallet] = window
|
|
||||||
return result
|
|
||||||
|
|
||||||
def is_available(self):
|
def is_available(self):
|
||||||
return bool(self.available_wallets())
|
return True
|
||||||
|
|
||||||
def update(self):
|
def update(self, window):
|
||||||
wallets = self.available_wallets()
|
wallet = window.wallet
|
||||||
if wallets:
|
if wallet.wallet_type not in ['2of2', '2of3']:
|
||||||
if self.listener is None:
|
return
|
||||||
self.print_error("starting listener")
|
if self.listener is None:
|
||||||
self.listener = Listener(self)
|
self.print_error("starting listener")
|
||||||
self.listener.start()
|
self.listener = Listener(self)
|
||||||
|
self.listener.start()
|
||||||
elif self.listener:
|
elif self.listener:
|
||||||
self.print_error("shutting down listener")
|
self.print_error("shutting down listener")
|
||||||
self.listener.stop()
|
self.listener.stop()
|
||||||
self.listener = None
|
self.listener = None
|
||||||
self.keys = []
|
self.keys = []
|
||||||
self.cosigner_list = []
|
self.cosigner_list = []
|
||||||
for wallet, window in wallets.items():
|
for key, xpub in wallet.master_public_keys.items():
|
||||||
for key, xpub in wallet.master_public_keys.items():
|
K = bitcoin.deserialize_xkey(xpub)[-1].encode('hex')
|
||||||
K = bitcoin.deserialize_xkey(xpub)[-1].encode('hex')
|
_hash = bitcoin.Hash(K).encode('hex')
|
||||||
_hash = bitcoin.Hash(K).encode('hex')
|
if wallet.master_private_keys.get(key):
|
||||||
if wallet.master_private_keys.get(key):
|
self.keys.append((key, _hash, window))
|
||||||
self.keys.append((key, _hash, window))
|
else:
|
||||||
else:
|
self.cosigner_list.append((window, xpub, K, _hash))
|
||||||
self.cosigner_list.append((window, xpub, K, _hash))
|
|
||||||
if self.listener:
|
if self.listener:
|
||||||
self.listener.set_keyhashes([t[1] for t in self.keys])
|
self.listener.set_keyhashes([t[1] for t in self.keys])
|
||||||
|
|
||||||
|
|
|
@ -129,10 +129,8 @@ class Plugin(BasePlugin):
|
||||||
self.obj.emit(SIGNAL('email:new_invoice'))
|
self.obj.emit(SIGNAL('email:new_invoice'))
|
||||||
|
|
||||||
def new_invoice(self):
|
def new_invoice(self):
|
||||||
if self.parent.windows:
|
self.parent.invoices.add(self.pr)
|
||||||
window = self.parent.windows[0]
|
#window.update_invoices_list()
|
||||||
window.invoices.add(self.pr)
|
|
||||||
window.update_invoices_list()
|
|
||||||
|
|
||||||
@hook
|
@hook
|
||||||
def receive_list_menu(self, menu, addr):
|
def receive_list_menu(self, menu, addr):
|
||||||
|
|
|
@ -30,10 +30,12 @@ CCY_PRECISIONS = {'BHD': 3, 'BIF': 0, 'BYR': 0, 'CLF': 4, 'CLP': 0,
|
||||||
'XPF': 0}
|
'XPF': 0}
|
||||||
|
|
||||||
class ExchangeBase(PrintError):
|
class ExchangeBase(PrintError):
|
||||||
def __init__(self, sig):
|
|
||||||
|
def __init__(self, on_quotes, on_history):
|
||||||
self.history = {}
|
self.history = {}
|
||||||
self.quotes = {}
|
self.quotes = {}
|
||||||
self.sig = sig
|
self.on_quotes = on_quotes
|
||||||
|
self.on_history = on_history
|
||||||
|
|
||||||
def protocol(self):
|
def protocol(self):
|
||||||
return "https"
|
return "https"
|
||||||
|
@ -59,7 +61,7 @@ class ExchangeBase(PrintError):
|
||||||
self.print_error("getting fx quotes for", ccy)
|
self.print_error("getting fx quotes for", ccy)
|
||||||
self.quotes = self.get_rates(ccy)
|
self.quotes = self.get_rates(ccy)
|
||||||
self.print_error("received fx quotes")
|
self.print_error("received fx quotes")
|
||||||
self.sig.emit(SIGNAL('fx_quotes'))
|
self.on_quotes()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.print_error("failed fx quotes:", e)
|
self.print_error("failed fx quotes:", e)
|
||||||
|
|
||||||
|
@ -73,7 +75,7 @@ class ExchangeBase(PrintError):
|
||||||
self.print_error("requesting fx history for", ccy)
|
self.print_error("requesting fx history for", ccy)
|
||||||
self.history[ccy] = self.historical_rates(ccy)
|
self.history[ccy] = self.historical_rates(ccy)
|
||||||
self.print_error("received fx history for", ccy)
|
self.print_error("received fx history for", ccy)
|
||||||
self.sig.emit(SIGNAL("fx_history"))
|
self.on_history()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.print_error("failed fx history:", e)
|
self.print_error("failed fx history:", e)
|
||||||
|
|
||||||
|
@ -241,16 +243,11 @@ class Plugin(BasePlugin, ThreadJob):
|
||||||
|
|
||||||
def __init__(self, parent, config, name):
|
def __init__(self, parent, config, name):
|
||||||
BasePlugin.__init__(self, parent, config, name)
|
BasePlugin.__init__(self, parent, config, name)
|
||||||
# Signal object first
|
|
||||||
self.sig = QObject()
|
|
||||||
self.sig.connect(self.sig, SIGNAL('fx_quotes'), self.on_fx_quotes)
|
|
||||||
self.sig.connect(self.sig, SIGNAL('fx_history'), self.on_fx_history)
|
|
||||||
self.ccy = self.config_ccy()
|
self.ccy = self.config_ccy()
|
||||||
self.history_used_spot = False
|
self.history_used_spot = False
|
||||||
self.ccy_combo = None
|
self.ccy_combo = None
|
||||||
self.hist_checkbox = None
|
self.hist_checkbox = None
|
||||||
self.windows = dict()
|
self.app = None
|
||||||
|
|
||||||
is_exchange = lambda obj: (inspect.isclass(obj)
|
is_exchange = lambda obj: (inspect.isclass(obj)
|
||||||
and issubclass(obj, ExchangeBase)
|
and issubclass(obj, ExchangeBase)
|
||||||
and obj != ExchangeBase)
|
and obj != ExchangeBase)
|
||||||
|
@ -268,7 +265,7 @@ class Plugin(BasePlugin, ThreadJob):
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# This runs from the network thread which catches exceptions
|
# This runs from the network thread which catches exceptions
|
||||||
if self.windows and self.timeout <= time.time():
|
if self.timeout <= time.time():
|
||||||
self.timeout = time.time() + 150
|
self.timeout = time.time() + 150
|
||||||
self.exchange.update(self.ccy)
|
self.exchange.update(self.ccy)
|
||||||
|
|
||||||
|
@ -285,24 +282,26 @@ class Plugin(BasePlugin, ThreadJob):
|
||||||
def show_history(self):
|
def show_history(self):
|
||||||
return self.config_history() and self.exchange.history_ccys()
|
return self.config_history() and self.exchange.history_ccys()
|
||||||
|
|
||||||
|
|
||||||
def set_exchange(self, name):
|
def set_exchange(self, name):
|
||||||
class_ = self.exchanges.get(name) or self.exchanges.values()[0]
|
class_ = self.exchanges.get(name) or self.exchanges.values()[0]
|
||||||
name = class_.__name__
|
name = class_.__name__
|
||||||
self.print_error("using exchange", name)
|
self.print_error("using exchange", name)
|
||||||
if self.config_exchange() != name:
|
if self.config_exchange() != name:
|
||||||
self.config.set_key('use_exchange', name, True)
|
self.config.set_key('use_exchange', name, True)
|
||||||
self.exchange = class_(self.sig)
|
|
||||||
|
on_quotes = lambda: self.app.emit(SIGNAL('new_fx_quotes'))
|
||||||
|
on_history = lambda: self.app.emit(SIGNAL('new_fx_history'))
|
||||||
|
self.exchange = class_(on_quotes, on_history)
|
||||||
# A new exchange means new fx quotes, initially empty. Force
|
# A new exchange means new fx quotes, initially empty. Force
|
||||||
# a quote refresh
|
# a quote refresh
|
||||||
self.timeout = 0
|
self.timeout = 0
|
||||||
self.get_historical_rates()
|
self.get_historical_rates()
|
||||||
self.on_fx_quotes()
|
#self.on_fx_quotes()
|
||||||
|
|
||||||
def update_status_bars(self):
|
|
||||||
'''Update status bar fiat balance in all windows'''
|
|
||||||
for window in self.windows:
|
|
||||||
window.update_status()
|
|
||||||
|
|
||||||
|
|
||||||
|
@hook
|
||||||
def on_new_window(self, window):
|
def on_new_window(self, window):
|
||||||
# Additional send and receive edit boxes
|
# Additional send and receive edit boxes
|
||||||
send_e = AmountEdit(self.config_ccy)
|
send_e = AmountEdit(self.config_ccy)
|
||||||
|
@ -311,20 +310,23 @@ class Plugin(BasePlugin, ThreadJob):
|
||||||
lambda: send_e.setFrozen(window.amount_e.isReadOnly()))
|
lambda: send_e.setFrozen(window.amount_e.isReadOnly()))
|
||||||
receive_e = AmountEdit(self.config_ccy)
|
receive_e = AmountEdit(self.config_ccy)
|
||||||
window.receive_grid.addWidget(receive_e, 2, 2, Qt.AlignLeft)
|
window.receive_grid.addWidget(receive_e, 2, 2, Qt.AlignLeft)
|
||||||
|
window.fiat_send_e = send_e
|
||||||
self.windows[window] = {'edits': (send_e, receive_e),
|
window.fiat_receive_e = receive_e
|
||||||
'last_edited': {}}
|
|
||||||
self.connect_fields(window, window.amount_e, send_e, window.fee_e)
|
self.connect_fields(window, window.amount_e, send_e, window.fee_e)
|
||||||
self.connect_fields(window, window.receive_amount_e, receive_e, None)
|
self.connect_fields(window, window.receive_amount_e, receive_e, None)
|
||||||
window.history_list.refresh_headers()
|
window.history_list.refresh_headers()
|
||||||
window.update_status()
|
window.update_status()
|
||||||
|
window.connect(window.app, SIGNAL('new_fx_quotes'), lambda: self.on_fx_quotes(window))
|
||||||
|
window.connect(window.app, SIGNAL('new_fx_history'), lambda: self.on_fx_history(window))
|
||||||
|
window.connect(window.app, SIGNAL('close_fx_plugin'), lambda: self.restore_window(window))
|
||||||
|
window.connect(window.app, SIGNAL('refresh_headers'), window.history_list.refresh_headers)
|
||||||
|
|
||||||
|
|
||||||
def connect_fields(self, window, btc_e, fiat_e, fee_e):
|
def connect_fields(self, window, btc_e, fiat_e, fee_e):
|
||||||
last_edited = self.windows[window]['last_edited']
|
|
||||||
|
|
||||||
def edit_changed(edit):
|
def edit_changed(edit):
|
||||||
edit.setStyleSheet(BLACK_FG)
|
edit.setStyleSheet(BLACK_FG)
|
||||||
last_edited[(fiat_e, btc_e)] = edit
|
fiat_e.is_last_edited = (edit == fiat_e)
|
||||||
amount = edit.get_amount()
|
amount = edit.get_amount()
|
||||||
rate = self.exchange_rate()
|
rate = self.exchange_rate()
|
||||||
if rate is None or amount is None:
|
if rate is None or amount is None:
|
||||||
|
@ -346,45 +348,43 @@ class Plugin(BasePlugin, ThreadJob):
|
||||||
|
|
||||||
fiat_e.textEdited.connect(partial(edit_changed, fiat_e))
|
fiat_e.textEdited.connect(partial(edit_changed, fiat_e))
|
||||||
btc_e.textEdited.connect(partial(edit_changed, btc_e))
|
btc_e.textEdited.connect(partial(edit_changed, btc_e))
|
||||||
last_edited[(fiat_e, btc_e)] = btc_e
|
fiat_e.is_last_edited = False
|
||||||
|
|
||||||
|
@hook
|
||||||
|
def init_qt(self, gui):
|
||||||
|
self.app = gui.app
|
||||||
|
|
||||||
@hook
|
@hook
|
||||||
def do_clear(self, window):
|
def do_clear(self, window):
|
||||||
self.windows[window]['edits'][0].setText('')
|
window.fiat_send_e.setText('')
|
||||||
|
|
||||||
def on_close_window(self, window):
|
|
||||||
self.windows.pop(window)
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
# Get rid of hooks before updating status bars.
|
# Get rid of hooks before updating status bars.
|
||||||
BasePlugin.close(self)
|
BasePlugin.close(self)
|
||||||
self.update_status_bars()
|
self.app.emit(SIGNAL('close_fx_plugin'))
|
||||||
self.refresh_headers()
|
|
||||||
for window, data in self.windows.items():
|
|
||||||
for edit in data['edits']:
|
|
||||||
edit.hide()
|
|
||||||
window.update_status()
|
|
||||||
|
|
||||||
def refresh_headers(self):
|
def restore_window(self, window):
|
||||||
for window in self.windows:
|
window.update_status()
|
||||||
window.history_list.refresh_headers()
|
window.history_list.refresh_headers()
|
||||||
|
window.fiat_send_e.hide()
|
||||||
|
window.fiat_receive_e.hide()
|
||||||
|
|
||||||
def on_fx_history(self):
|
def on_fx_history(self, window):
|
||||||
'''Called when historical fx quotes are updated'''
|
'''Called when historical fx quotes are updated'''
|
||||||
for window in self.windows:
|
window.history_list.update()
|
||||||
window.history_list.update()
|
|
||||||
|
|
||||||
def on_fx_quotes(self):
|
def on_fx_quotes(self, window):
|
||||||
'''Called when fresh spot fx quotes come in'''
|
'''Called when fresh spot fx quotes come in'''
|
||||||
self.update_status_bars()
|
window.update_status()
|
||||||
self.populate_ccy_combo()
|
self.populate_ccy_combo()
|
||||||
# Refresh edits with the new rate
|
# Refresh edits with the new rate
|
||||||
for window, data in self.windows.items():
|
edit = window.fiat_send_e if window.fiat_send_e.is_last_edited else window.amount_e
|
||||||
for edit in data['last_edited'].values():
|
edit.textEdited.emit(edit.text())
|
||||||
edit.textEdited.emit(edit.text())
|
edit = window.fiat_receive_e if window.fiat_receive_e.is_last_edited else window.receive_amount_e
|
||||||
|
edit.textEdited.emit(edit.text())
|
||||||
# History tab needs updating if it used spot
|
# History tab needs updating if it used spot
|
||||||
if self.history_used_spot:
|
if self.history_used_spot:
|
||||||
self.on_fx_history()
|
self.on_fx_history(window)
|
||||||
|
|
||||||
def on_ccy_combo_change(self):
|
def on_ccy_combo_change(self):
|
||||||
'''Called when the chosen currency changes'''
|
'''Called when the chosen currency changes'''
|
||||||
|
@ -392,7 +392,7 @@ class Plugin(BasePlugin, ThreadJob):
|
||||||
if ccy and ccy != self.ccy:
|
if ccy and ccy != self.ccy:
|
||||||
self.ccy = ccy
|
self.ccy = ccy
|
||||||
self.config.set_key('currency', ccy, True)
|
self.config.set_key('currency', ccy, True)
|
||||||
self.update_status_bars()
|
self.app.emit(SIGNAL('new_fx_quotes'))
|
||||||
self.get_historical_rates() # Because self.ccy changes
|
self.get_historical_rates() # Because self.ccy changes
|
||||||
self.hist_checkbox_update()
|
self.hist_checkbox_update()
|
||||||
|
|
||||||
|
@ -503,7 +503,7 @@ class Plugin(BasePlugin, ThreadJob):
|
||||||
self.get_historical_rates()
|
self.get_historical_rates()
|
||||||
else:
|
else:
|
||||||
self.config.set_key('history_rates', 'unchecked')
|
self.config.set_key('history_rates', 'unchecked')
|
||||||
self.refresh_headers()
|
self.app.emit(SIGNAL('refresh_headers'))
|
||||||
|
|
||||||
def ok_clicked():
|
def ok_clicked():
|
||||||
self.timeout = 0
|
self.timeout = 0
|
||||||
|
|
|
@ -35,6 +35,7 @@ class Plugin(BasePlugin):
|
||||||
self.obj = QObject()
|
self.obj = QObject()
|
||||||
self.obj.connect(self.obj, SIGNAL('labels:pulled'), self.on_pulled)
|
self.obj.connect(self.obj, SIGNAL('labels:pulled'), self.on_pulled)
|
||||||
|
|
||||||
|
@hook
|
||||||
def on_new_window(self, window):
|
def on_new_window(self, window):
|
||||||
wallet = window.wallet
|
wallet = window.wallet
|
||||||
nonce = self.get_nonce(wallet)
|
nonce = self.get_nonce(wallet)
|
||||||
|
@ -53,6 +54,7 @@ class Plugin(BasePlugin):
|
||||||
t.setDaemon(True)
|
t.setDaemon(True)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
|
@hook
|
||||||
def on_close_window(self, window):
|
def on_close_window(self, window):
|
||||||
self.wallets.pop(window.wallet)
|
self.wallets.pop(window.wallet)
|
||||||
|
|
||||||
|
|
|
@ -177,6 +177,8 @@ class Wallet_2fa(Multisig_Wallet):
|
||||||
self.wallet_type = '2fa'
|
self.wallet_type = '2fa'
|
||||||
self.m = 2
|
self.m = 2
|
||||||
self.n = 3
|
self.n = 3
|
||||||
|
self.is_billing = False
|
||||||
|
self.billing_info = None
|
||||||
|
|
||||||
def get_action(self):
|
def get_action(self):
|
||||||
xpub1 = self.master_public_keys.get("x1/")
|
xpub1 = self.master_public_keys.get("x1/")
|
||||||
|
@ -194,30 +196,69 @@ class Wallet_2fa(Multisig_Wallet):
|
||||||
def make_seed(self):
|
def make_seed(self):
|
||||||
return Mnemonic('english').make_seed(num_bits=256, prefix=SEED_PREFIX)
|
return Mnemonic('english').make_seed(num_bits=256, prefix=SEED_PREFIX)
|
||||||
|
|
||||||
|
def can_sign_without_server(self):
|
||||||
|
return self.master_private_keys.get('x2/') is not None
|
||||||
|
|
||||||
|
def extra_fee(self, tx):
|
||||||
|
if self.can_sign_without_server():
|
||||||
|
return 0
|
||||||
|
if self.billing_info.get('tx_remaining'):
|
||||||
|
return 0
|
||||||
|
if self.is_billing:
|
||||||
|
return 0
|
||||||
|
# trustedcoin won't charge if the total inputs is lower than their fee
|
||||||
|
price = int(self.price_per_tx.get(1))
|
||||||
|
assert price <= 100000
|
||||||
|
if tx.input_value() < price:
|
||||||
|
self.print_error("not charging for this tx")
|
||||||
|
return 0
|
||||||
|
return price
|
||||||
|
|
||||||
def estimated_fee(self, tx, fee_per_kb):
|
def estimated_fee(self, tx, fee_per_kb):
|
||||||
fee = Multisig_Wallet.estimated_fee(self, tx, fee_per_kb)
|
fee = Multisig_Wallet.estimated_fee(self, tx, fee_per_kb)
|
||||||
x = run_hook('extra_fee', self, tx)
|
fee += self.extra_fee(tx)
|
||||||
if x: fee += x
|
|
||||||
return fee
|
return fee
|
||||||
|
|
||||||
def get_tx_fee(self, tx):
|
def get_tx_fee(self, tx):
|
||||||
fee = Multisig_Wallet.get_tx_fee(self, tx)
|
fee = Multisig_Wallet.get_tx_fee(self, tx)
|
||||||
x = run_hook('extra_fee', self, tx)
|
fee += self.extra_fee(tx)
|
||||||
if x: fee += x
|
|
||||||
return fee
|
return fee
|
||||||
|
|
||||||
|
def make_unsigned_transaction(self, *args):
|
||||||
|
tx = BIP32_Wallet.make_unsigned_transaction(self, *args)
|
||||||
|
fee = self.extra_fee(tx)
|
||||||
|
if fee:
|
||||||
|
address = self.billing_info['billing_address']
|
||||||
|
tx.outputs.append(('address', address, fee))
|
||||||
|
return tx
|
||||||
|
|
||||||
|
def sign_transaction(self, tx, password):
|
||||||
|
BIP32_Wallet.sign_transaction(self, tx, password)
|
||||||
|
if tx.is_complete():
|
||||||
|
return
|
||||||
|
if not self.auth_code:
|
||||||
|
self.print_error("sign_transaction: no auth code")
|
||||||
|
return
|
||||||
|
long_user_id, short_id = self.get_user_id()
|
||||||
|
tx_dict = tx.as_dict()
|
||||||
|
raw_tx = tx_dict["hex"]
|
||||||
|
r = server.sign(short_id, raw_tx, self.auth_code)
|
||||||
|
if r:
|
||||||
|
raw_tx = r.get('transaction')
|
||||||
|
tx.update(raw_tx)
|
||||||
|
self.print_error("twofactor: is complete", tx.is_complete())
|
||||||
|
|
||||||
|
def get_user_id(self):
|
||||||
|
def make_long_id(xpub_hot, xpub_cold):
|
||||||
|
return bitcoin.sha256(''.join(sorted([xpub_hot, xpub_cold])))
|
||||||
|
xpub_hot = self.master_public_keys["x1/"]
|
||||||
|
xpub_cold = self.master_public_keys["x2/"]
|
||||||
|
long_id = make_long_id(xpub_hot, xpub_cold)
|
||||||
|
short_id = hashlib.sha256(long_id).hexdigest()
|
||||||
|
return long_id, short_id
|
||||||
|
|
||||||
# Utility functions
|
# Utility functions
|
||||||
|
|
||||||
def get_user_id(wallet):
|
|
||||||
def make_long_id(xpub_hot, xpub_cold):
|
|
||||||
return bitcoin.sha256(''.join(sorted([xpub_hot, xpub_cold])))
|
|
||||||
|
|
||||||
xpub_hot = wallet.master_public_keys["x1/"]
|
|
||||||
xpub_cold = wallet.master_public_keys["x2/"]
|
|
||||||
long_id = make_long_id(xpub_hot, xpub_cold)
|
|
||||||
short_id = hashlib.sha256(long_id).hexdigest()
|
|
||||||
return long_id, short_id
|
|
||||||
|
|
||||||
def make_xpub(xpub, s):
|
def make_xpub(xpub, s):
|
||||||
_, _, _, c, cK = deserialize_xkey(xpub)
|
_, _, _, c, cK = deserialize_xkey(xpub)
|
||||||
cK2, c2 = bitcoin._CKD_pub(cK, c, s)
|
cK2, c2 = bitcoin._CKD_pub(cK, c, s)
|
||||||
|
@ -225,12 +266,12 @@ def make_xpub(xpub, s):
|
||||||
return EncodeBase58Check(xpub2)
|
return EncodeBase58Check(xpub2)
|
||||||
|
|
||||||
def restore_third_key(wallet):
|
def restore_third_key(wallet):
|
||||||
long_user_id, short_id = get_user_id(wallet)
|
long_user_id, short_id = wallet.get_user_id()
|
||||||
xpub3 = make_xpub(signing_xpub, long_user_id)
|
xpub3 = make_xpub(signing_xpub, long_user_id)
|
||||||
wallet.add_master_public_key('x3/', xpub3)
|
wallet.add_master_public_key('x3/', xpub3)
|
||||||
|
|
||||||
def make_billing_address(wallet, num):
|
def make_billing_address(wallet, num):
|
||||||
long_id, short_id = get_user_id(wallet)
|
long_id, short_id = wallet.get_user_id()
|
||||||
xpub = make_xpub(billing_xpub, long_id)
|
xpub = make_xpub(billing_xpub, long_id)
|
||||||
_, _, _, c, cK = deserialize_xkey(xpub)
|
_, _, _, c, cK = deserialize_xkey(xpub)
|
||||||
cK, c = bitcoin.CKD_pub(cK, c, num)
|
cK, c = bitcoin.CKD_pub(cK, c, num)
|
||||||
|
@ -240,7 +281,7 @@ def make_billing_address(wallet, num):
|
||||||
def need_server(wallet, tx):
|
def need_server(wallet, tx):
|
||||||
from electrum.account import BIP32_Account
|
from electrum.account import BIP32_Account
|
||||||
# Detect if the server is needed
|
# Detect if the server is needed
|
||||||
long_id, short_id = get_user_id(wallet)
|
long_id, short_id = wallet.get_user_id()
|
||||||
xpub3 = wallet.master_public_keys['x3/']
|
xpub3 = wallet.master_public_keys['x3/']
|
||||||
for x in tx.inputs_to_sign():
|
for x in tx.inputs_to_sign():
|
||||||
if x[0:2] == 'ff':
|
if x[0:2] == 'ff':
|
||||||
|
@ -254,25 +295,20 @@ class Plugin(BasePlugin):
|
||||||
def __init__(self, parent, config, name):
|
def __init__(self, parent, config, name):
|
||||||
BasePlugin.__init__(self, parent, config, name)
|
BasePlugin.__init__(self, parent, config, name)
|
||||||
self.seed_func = lambda x: bitcoin.is_new_seed(x, SEED_PREFIX)
|
self.seed_func = lambda x: bitcoin.is_new_seed(x, SEED_PREFIX)
|
||||||
# Keyed by wallet to handle multiple pertinent windows. Each
|
|
||||||
# wallet is a 2fa wallet. Each value is a dictionary with
|
|
||||||
# information about the wallet for the plugin
|
|
||||||
self.wallets = {}
|
|
||||||
|
|
||||||
def constructor(self, s):
|
def constructor(self, s):
|
||||||
return Wallet_2fa(s)
|
return Wallet_2fa(s)
|
||||||
|
|
||||||
def is_available(self):
|
def is_available(self):
|
||||||
return bool(self.wallets)
|
return True
|
||||||
|
|
||||||
def set_enabled(self, wallet, enabled):
|
def set_enabled(self, wallet, enabled):
|
||||||
wallet.storage.put('use_' + self.name, enabled)
|
wallet.storage.put('use_' + self.name, enabled)
|
||||||
|
|
||||||
def is_enabled(self):
|
def is_enabled(self):
|
||||||
return (self.is_available() and
|
return True
|
||||||
not all(wallet.master_private_keys.get('x2/')
|
|
||||||
for wallet in self.wallets.keys()))
|
|
||||||
|
|
||||||
|
@hook
|
||||||
def on_new_window(self, window):
|
def on_new_window(self, window):
|
||||||
wallet = window.wallet
|
wallet = window.wallet
|
||||||
if wallet.storage.get('wallet_type') == '2fa':
|
if wallet.storage.get('wallet_type') == '2fa':
|
||||||
|
@ -280,25 +316,16 @@ class Plugin(BasePlugin):
|
||||||
_("TrustedCoin"),
|
_("TrustedCoin"),
|
||||||
partial(self.settings_dialog, window))
|
partial(self.settings_dialog, window))
|
||||||
window.statusBar().addPermanentWidget(button)
|
window.statusBar().addPermanentWidget(button)
|
||||||
self.wallets[wallet] = {
|
|
||||||
'is_billing' : False,
|
|
||||||
'billing_info' : None,
|
|
||||||
'button' : button, # Avoid loss to GC
|
|
||||||
}
|
|
||||||
t = Thread(target=self.request_billing_info, args=(wallet,))
|
t = Thread(target=self.request_billing_info, args=(wallet,))
|
||||||
t.setDaemon(True)
|
t.setDaemon(True)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
def on_close_window(self, window):
|
|
||||||
self.wallets.pop(window.wallet, None)
|
|
||||||
|
|
||||||
def request_billing_info(self, wallet):
|
def request_billing_info(self, wallet):
|
||||||
billing_info = server.get(get_user_id(wallet)[1])
|
billing_info = server.get(wallet.get_user_id()[1])
|
||||||
billing_address = make_billing_address(wallet, billing_info['billing_index'])
|
billing_address = make_billing_address(wallet, billing_info['billing_index'])
|
||||||
assert billing_address == billing_info['billing_address']
|
assert billing_address == billing_info['billing_address']
|
||||||
wallet_info = self.wallets[wallet]
|
wallet.billing_info = billing_info
|
||||||
wallet_info['billing_info'] = billing_info
|
wallet.price_per_tx = dict(billing_info['price_per_tx'])
|
||||||
wallet_info['price_per_tx'] = dict(billing_info['price_per_tx'])
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def create_extended_seed(self, wallet, window):
|
def create_extended_seed(self, wallet, window):
|
||||||
|
@ -352,7 +379,7 @@ class Plugin(BasePlugin):
|
||||||
|
|
||||||
@hook
|
@hook
|
||||||
def do_clear(self, window):
|
def do_clear(self, window):
|
||||||
self.wallets[window.wallet]['is_billing'] = False
|
window.wallet.is_billing = False
|
||||||
|
|
||||||
@hook
|
@hook
|
||||||
def get_wizard_action(self, window, wallet, action):
|
def get_wizard_action(self, window, wallet, action):
|
||||||
|
@ -396,7 +423,7 @@ class Plugin(BasePlugin):
|
||||||
xpub_cold = wallet.master_public_keys["x2/"]
|
xpub_cold = wallet.master_public_keys["x2/"]
|
||||||
|
|
||||||
# Generate third key deterministically.
|
# Generate third key deterministically.
|
||||||
long_user_id, short_id = get_user_id(wallet)
|
long_user_id, short_id = wallet.get_user_id()
|
||||||
xpub3 = make_xpub(signing_xpub, long_user_id)
|
xpub3 = make_xpub(signing_xpub, long_user_id)
|
||||||
|
|
||||||
# secret must be sent by the server
|
# secret must be sent by the server
|
||||||
|
@ -453,86 +480,30 @@ class Plugin(BasePlugin):
|
||||||
@hook
|
@hook
|
||||||
def sign_tx(self, window, tx):
|
def sign_tx(self, window, tx):
|
||||||
self.print_error("twofactor:sign_tx")
|
self.print_error("twofactor:sign_tx")
|
||||||
if window.wallet in self.wallets:
|
wallet = window.wallet
|
||||||
|
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server():
|
||||||
auth_code = None
|
auth_code = None
|
||||||
if need_server(window.wallet, tx):
|
if need_server(wallet, tx):
|
||||||
auth_code = self.auth_dialog(window)
|
auth_code = self.auth_dialog(window)
|
||||||
else:
|
else:
|
||||||
self.print_error("twofactor: xpub3 not needed")
|
self.print_error("twofactor: xpub3 not needed")
|
||||||
self.wallets[window.wallet]['auth_code'] = auth_code
|
window.wallet.auth_code = auth_code
|
||||||
|
|
||||||
@hook
|
@hook
|
||||||
def abort_send(self, window):
|
def abort_send(self, window):
|
||||||
if window.wallet in self.wallets:
|
wallet = window.wallet
|
||||||
wallet_info = self.wallets[window.wallet]
|
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server():
|
||||||
# request billing info before forming the transaction
|
if wallet.billing_info is None:
|
||||||
wallet_info['billing_info'] = None
|
# request billing info before forming the transaction
|
||||||
task = partial(self.request_billing_info, window.wallet)
|
task = partial(self.request_billing_info, wallet)
|
||||||
waiting_dialog = WaitingDialog(window, 'please wait...', task)
|
waiting_dialog = WaitingDialog(window, 'please wait...', task)
|
||||||
waiting_dialog.start()
|
waiting_dialog.start()
|
||||||
waiting_dialog.wait()
|
waiting_dialog.wait()
|
||||||
if wallet_info['billing_info'] is None:
|
if wallet.billing_info is None:
|
||||||
window.show_message('Could not contact server')
|
window.show_message('Could not contact server')
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@hook
|
|
||||||
def extra_fee(self, wallet, tx):
|
|
||||||
if not wallet in self.wallets:
|
|
||||||
return 0
|
|
||||||
wallet_info = self.wallets[wallet]
|
|
||||||
if wallet_info['billing_info'].get('tx_remaining'):
|
|
||||||
return 0
|
|
||||||
if wallet_info['is_billing']:
|
|
||||||
return 0
|
|
||||||
# trustedcoin won't charge if the total inputs is lower than their fee
|
|
||||||
price = int(wallet_info['price_per_tx'].get(1))
|
|
||||||
assert price <= 100000
|
|
||||||
if tx.input_value() < price:
|
|
||||||
self.print_error("not charging for this tx")
|
|
||||||
return 0
|
|
||||||
return price
|
|
||||||
|
|
||||||
@hook
|
|
||||||
def make_unsigned_transaction(self, wallet, tx):
|
|
||||||
if wallet in self.wallets:
|
|
||||||
price = self.extra_fee(wallet, tx)
|
|
||||||
if not price:
|
|
||||||
return
|
|
||||||
address = self.wallets[wallet]['billing_info']['billing_address']
|
|
||||||
tx.outputs.append(('address', address, price))
|
|
||||||
|
|
||||||
@hook
|
|
||||||
def sign_transaction(self, wallet, tx, password):
|
|
||||||
self.print_error("twofactor:sign")
|
|
||||||
if not wallet in self.wallets:
|
|
||||||
self.print_error("not 2fa wallet")
|
|
||||||
return
|
|
||||||
|
|
||||||
auth_code = self.wallets[wallet]['auth_code']
|
|
||||||
if not auth_code:
|
|
||||||
self.print_error("sign_transaction: no auth code")
|
|
||||||
return
|
|
||||||
|
|
||||||
if tx.is_complete():
|
|
||||||
return
|
|
||||||
|
|
||||||
long_user_id, short_id = get_user_id(wallet)
|
|
||||||
tx_dict = tx.as_dict()
|
|
||||||
raw_tx = tx_dict["hex"]
|
|
||||||
try:
|
|
||||||
r = server.sign(short_id, raw_tx, auth_code)
|
|
||||||
except Exception as e:
|
|
||||||
tx.error = str(e)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.print_error( "received answer", r)
|
|
||||||
if not r:
|
|
||||||
return
|
|
||||||
|
|
||||||
raw_tx = r.get('transaction')
|
|
||||||
tx.update(raw_tx)
|
|
||||||
self.print_error("twofactor: is complete", tx.is_complete())
|
|
||||||
|
|
||||||
def settings_dialog(self, window):
|
def settings_dialog(self, window):
|
||||||
task = partial(self.request_billing_info, window.wallet)
|
task = partial(self.request_billing_info, window.wallet)
|
||||||
|
@ -576,7 +547,7 @@ class Plugin(BasePlugin):
|
||||||
grid = QGridLayout()
|
grid = QGridLayout()
|
||||||
vbox.addLayout(grid)
|
vbox.addLayout(grid)
|
||||||
|
|
||||||
price_per_tx = self.wallets[wallet]['price_per_tx']
|
price_per_tx = wallet.price_per_tx
|
||||||
v = price_per_tx.get(1)
|
v = price_per_tx.get(1)
|
||||||
grid.addWidget(QLabel(_("Price per transaction (not prepaid):")), 0, 0)
|
grid.addWidget(QLabel(_("Price per transaction (not prepaid):")), 0, 0)
|
||||||
grid.addWidget(QLabel(window.format_amount(v) + ' ' + window.base_unit()), 0, 1)
|
grid.addWidget(QLabel(window.format_amount(v) + ' ' + window.base_unit()), 0, 1)
|
||||||
|
@ -596,7 +567,7 @@ class Plugin(BasePlugin):
|
||||||
grid.addWidget(b, i, 2)
|
grid.addWidget(b, i, 2)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
n = self.wallets[wallet]['billing_info'].get('tx_remaining', 0)
|
n = wallet.billing_info.get('tx_remaining', 0)
|
||||||
grid.addWidget(QLabel(_("Your wallet has %d prepaid transactions.")%n), i, 0)
|
grid.addWidget(QLabel(_("Your wallet has %d prepaid transactions.")%n), i, 0)
|
||||||
|
|
||||||
# tranfer button
|
# tranfer button
|
||||||
|
@ -616,9 +587,9 @@ class Plugin(BasePlugin):
|
||||||
d.close()
|
d.close()
|
||||||
if window.pluginsdialog:
|
if window.pluginsdialog:
|
||||||
window.pluginsdialog.close()
|
window.pluginsdialog.close()
|
||||||
wallet_info = self.wallets[window.wallet]
|
wallet = window.wallet
|
||||||
uri = "bitcoin:" + wallet_info['billing_info']['billing_address'] + "?message=TrustedCoin %d Prepaid Transactions&amount="%k + str(Decimal(v)/100000000)
|
uri = "bitcoin:" + wallet.billing_info['billing_address'] + "?message=TrustedCoin %d Prepaid Transactions&amount="%k + str(Decimal(v)/100000000)
|
||||||
wallet_info['is_billing'] = True
|
wallet.is_billing = True
|
||||||
window.pay_to_URI(uri)
|
window.pay_to_URI(uri)
|
||||||
window.payto_e.setFrozen(True)
|
window.payto_e.setFrozen(True)
|
||||||
window.message_e.setFrozen(True)
|
window.message_e.setFrozen(True)
|
||||||
|
|
Loading…
Reference in New Issue