ThomasV
9 years ago
36 changed files with 997 additions and 966 deletions
@ -0,0 +1,7 @@ |
|||
from electrum.i18n import _ |
|||
|
|||
fullname = _('Audio MODEM') |
|||
description = _('Provides support for air-gapped transaction signing.') |
|||
requires = [('amodem', 'http://github.com/romanz/amodem/')] |
|||
available_for = ['qt'] |
|||
|
@ -0,0 +1,9 @@ |
|||
from electrum.i18n import _ |
|||
fullname = _('Cosigner Pool') |
|||
description = ' '.join([ |
|||
_("This plugin facilitates the use of multi-signatures wallets."), |
|||
_("It sends and receives partially signed transactions from/to your cosigner wallet."), |
|||
_("Transactions are encrypted and stored on a remote server.") |
|||
]) |
|||
requires_wallet_type = ['2of2', '2of3'] |
|||
available_for = ['qt'] |
@ -0,0 +1,5 @@ |
|||
from electrum.i18n import _ |
|||
|
|||
fullname = _('Email') |
|||
description = _("Send and receive payment request with an email account") |
|||
available_for = ['qt'] |
@ -0,0 +1,5 @@ |
|||
from electrum.i18n import _ |
|||
|
|||
fullname = _("Exchange rates") |
|||
description = _("Exchange rates and currency conversion tools.") |
|||
available_for = ['qt','kivy'] |
@ -0,0 +1,3 @@ |
|||
from exchange_rate import FxPlugin |
|||
class Plugin(FxPlugin): |
|||
pass |
@ -0,0 +1,184 @@ |
|||
from PyQt4.QtGui import * |
|||
from PyQt4.QtCore import * |
|||
from electrum_gui.qt.util import * |
|||
from electrum_gui.qt.amountedit import AmountEdit |
|||
|
|||
|
|||
from electrum.bitcoin import COIN |
|||
from electrum.i18n import _ |
|||
from decimal import Decimal |
|||
from functools import partial |
|||
from electrum.plugins import hook |
|||
from exchange_rate import FxPlugin |
|||
|
|||
class Plugin(FxPlugin): |
|||
|
|||
def connect_fields(self, window, btc_e, fiat_e, fee_e): |
|||
|
|||
def edit_changed(edit): |
|||
edit.setStyleSheet(BLACK_FG) |
|||
fiat_e.is_last_edited = (edit == fiat_e) |
|||
amount = edit.get_amount() |
|||
rate = self.exchange_rate() |
|||
if rate is None or amount is None: |
|||
if edit is fiat_e: |
|||
btc_e.setText("") |
|||
if fee_e: |
|||
fee_e.setText("") |
|||
else: |
|||
fiat_e.setText("") |
|||
else: |
|||
if edit is fiat_e: |
|||
btc_e.setAmount(int(amount / Decimal(rate) * COIN)) |
|||
if fee_e: window.update_fee() |
|||
btc_e.setStyleSheet(BLUE_FG) |
|||
else: |
|||
fiat_e.setText(self.ccy_amount_str( |
|||
amount * Decimal(rate) / COIN, False)) |
|||
fiat_e.setStyleSheet(BLUE_FG) |
|||
|
|||
fiat_e.textEdited.connect(partial(edit_changed, fiat_e)) |
|||
btc_e.textEdited.connect(partial(edit_changed, btc_e)) |
|||
fiat_e.is_last_edited = False |
|||
|
|||
@hook |
|||
def init_qt(self, gui): |
|||
self.app = gui.app |
|||
|
|||
@hook |
|||
def do_clear(self, window): |
|||
window.fiat_send_e.setText('') |
|||
|
|||
def close(self): |
|||
# Get rid of hooks before updating status bars. |
|||
FxPlugin.close(self) |
|||
self.app.emit(SIGNAL('close_fx_plugin')) |
|||
|
|||
def restore_window(self, window): |
|||
window.update_status() |
|||
window.history_list.refresh_headers() |
|||
window.fiat_send_e.hide() |
|||
window.fiat_receive_e.hide() |
|||
|
|||
def on_quotes(self): |
|||
self.app.emit(SIGNAL('new_fx_quotes')) |
|||
|
|||
def on_history(self): |
|||
self.app.emit(SIGNAL('new_fx_history')) |
|||
|
|||
def on_fx_history(self, window): |
|||
'''Called when historical fx quotes are updated''' |
|||
window.history_list.update() |
|||
|
|||
def on_fx_quotes(self, window): |
|||
'''Called when fresh spot fx quotes come in''' |
|||
window.update_status() |
|||
self.populate_ccy_combo() |
|||
# Refresh edits with the new rate |
|||
edit = window.fiat_send_e if window.fiat_send_e.is_last_edited else window.amount_e |
|||
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 |
|||
if self.history_used_spot: |
|||
self.on_fx_history(window) |
|||
|
|||
def on_ccy_combo_change(self): |
|||
'''Called when the chosen currency changes''' |
|||
ccy = str(self.ccy_combo.currentText()) |
|||
if ccy and ccy != self.ccy: |
|||
self.ccy = ccy |
|||
self.config.set_key('currency', ccy, True) |
|||
self.app.emit(SIGNAL('new_fx_quotes')) |
|||
self.get_historical_rates() # Because self.ccy changes |
|||
self.hist_checkbox_update() |
|||
|
|||
def hist_checkbox_update(self): |
|||
if self.hist_checkbox: |
|||
self.hist_checkbox.setEnabled(self.ccy in self.exchange.history_ccys()) |
|||
self.hist_checkbox.setChecked(self.config_history()) |
|||
|
|||
def populate_ccy_combo(self): |
|||
# There should be at most one instance of the settings dialog |
|||
combo = self.ccy_combo |
|||
# NOTE: bool(combo) is False if it is empty. Nuts. |
|||
if combo is not None: |
|||
combo.blockSignals(True) |
|||
combo.clear() |
|||
combo.addItems(sorted(self.exchange.quotes.keys())) |
|||
combo.blockSignals(False) |
|||
combo.setCurrentIndex(combo.findText(self.ccy)) |
|||
|
|||
@hook |
|||
def on_new_window(self, window): |
|||
# Additional send and receive edit boxes |
|||
send_e = AmountEdit(self.config_ccy) |
|||
window.send_grid.addWidget(send_e, 4, 2, Qt.AlignLeft) |
|||
window.amount_e.frozen.connect( |
|||
lambda: send_e.setFrozen(window.amount_e.isReadOnly())) |
|||
receive_e = AmountEdit(self.config_ccy) |
|||
window.receive_grid.addWidget(receive_e, 2, 2, Qt.AlignLeft) |
|||
window.fiat_send_e = send_e |
|||
window.fiat_receive_e = receive_e |
|||
self.connect_fields(window, window.amount_e, send_e, window.fee_e) |
|||
self.connect_fields(window, window.receive_amount_e, receive_e, None) |
|||
window.history_list.refresh_headers() |
|||
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 settings_widget(self, window): |
|||
return EnterButton(_('Settings'), self.settings_dialog) |
|||
|
|||
def settings_dialog(self): |
|||
d = QDialog() |
|||
d.setWindowTitle("Settings") |
|||
layout = QGridLayout(d) |
|||
layout.addWidget(QLabel(_('Exchange rate API: ')), 0, 0) |
|||
layout.addWidget(QLabel(_('Currency: ')), 1, 0) |
|||
layout.addWidget(QLabel(_('History Rates: ')), 2, 0) |
|||
|
|||
# Currency list |
|||
self.ccy_combo = QComboBox() |
|||
self.ccy_combo.currentIndexChanged.connect(self.on_ccy_combo_change) |
|||
self.populate_ccy_combo() |
|||
|
|||
def on_change_ex(idx): |
|||
exchange = str(combo_ex.currentText()) |
|||
if exchange != self.exchange.name(): |
|||
self.set_exchange(exchange) |
|||
self.hist_checkbox_update() |
|||
|
|||
def on_change_hist(checked): |
|||
if checked: |
|||
self.config.set_key('history_rates', 'checked') |
|||
self.get_historical_rates() |
|||
else: |
|||
self.config.set_key('history_rates', 'unchecked') |
|||
self.app.emit(SIGNAL('refresh_headers')) |
|||
|
|||
def ok_clicked(): |
|||
self.timeout = 0 |
|||
self.ccy_combo = None |
|||
d.accept() |
|||
|
|||
combo_ex = QComboBox() |
|||
combo_ex.addItems(sorted(self.exchanges.keys())) |
|||
combo_ex.setCurrentIndex(combo_ex.findText(self.config_exchange())) |
|||
combo_ex.currentIndexChanged.connect(on_change_ex) |
|||
|
|||
self.hist_checkbox = QCheckBox() |
|||
self.hist_checkbox.stateChanged.connect(on_change_hist) |
|||
self.hist_checkbox_update() |
|||
|
|||
ok_button = QPushButton(_("OK")) |
|||
ok_button.clicked.connect(lambda: ok_clicked()) |
|||
|
|||
layout.addWidget(self.ccy_combo,1,1) |
|||
layout.addWidget(combo_ex,0,1) |
|||
layout.addWidget(self.hist_checkbox,2,1) |
|||
layout.addWidget(ok_button,3,1) |
|||
|
|||
return d.exec_() |
@ -0,0 +1,5 @@ |
|||
from electrum.i18n import _ |
|||
|
|||
fullname = 'GreenAddress instant' |
|||
description = _("Allows validating if your transactions have instant confirmations by GreenAddress") |
|||
available_for = ['qt'] |
@ -0,0 +1,8 @@ |
|||
from electrum.i18n import _ |
|||
|
|||
fullname = 'KeepKey' |
|||
description = _('Provides support for KeepKey hardware wallet') |
|||
requires = [('keepkeylib','github.com/keepkey/python-keepkey')] |
|||
requires_wallet_type = ['keepkey'] |
|||
registers_wallet_type = ('hardware', 'keepkey', _("KeepKey wallet")) |
|||
available_for = ['qt', 'cmdline'] |
@ -0,0 +1,95 @@ |
|||
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton |
|||
import PyQt4.QtCore as QtCore |
|||
from electrum_gui.qt.util import * |
|||
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow |
|||
from electrum_gui.qt.installwizard import InstallWizard |
|||
from keepkeylib.qt.pinmatrix import PinMatrixWidget |
|||
|
|||
from keepkey import KeepKeyPlugin |
|||
|
|||
class Plugin(KeepKeyPlugin): |
|||
|
|||
@hook |
|||
def load_wallet(self, wallet, window): |
|||
self.print_error("load_wallet") |
|||
self.wallet = wallet |
|||
self.wallet.plugin = self |
|||
self.keepkey_button = StatusBarButton(QIcon(":icons/keepkey.png"), _("KeepKey"), partial(self.settings_dialog, window)) |
|||
if type(window) is ElectrumWindow: |
|||
window.statusBar().addPermanentWidget(self.keepkey_button) |
|||
if self.handler is None: |
|||
self.handler = KeepKeyQtHandler(window) |
|||
try: |
|||
self.get_client().ping('t') |
|||
except BaseException as e: |
|||
QMessageBox.information(window, _('Error'), _("KeepKey device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK')) |
|||
self.wallet.force_watching_only = True |
|||
return |
|||
if self.wallet.addresses() and not self.wallet.check_proper_device(): |
|||
QMessageBox.information(window, _('Error'), _("This wallet does not match your KeepKey device"), _('OK')) |
|||
self.wallet.force_watching_only = True |
|||
|
|||
@hook |
|||
def installwizard_load_wallet(self, wallet, window): |
|||
if type(wallet) != KeepKeyWallet: |
|||
return |
|||
self.load_wallet(wallet, window) |
|||
|
|||
@hook |
|||
def installwizard_restore(self, wizard, storage): |
|||
if storage.get('wallet_type') != 'keepkey': |
|||
return |
|||
seed = wizard.enter_seed_dialog("Enter your KeepKey seed", None, func=lambda x:True) |
|||
if not seed: |
|||
return |
|||
wallet = KeepKeyWallet(storage) |
|||
self.wallet = wallet |
|||
handler = KeepKeyQtHandler(wizard) |
|||
passphrase = handler.get_passphrase(_("Please enter your KeepKey passphrase.") + '\n' + _("Press OK if you do not use one.")) |
|||
if passphrase is None: |
|||
return |
|||
password = wizard.password_dialog() |
|||
wallet.add_seed(seed, password) |
|||
wallet.add_cosigner_seed(seed, 'x/', password, passphrase) |
|||
wallet.create_main_account(password) |
|||
# disable keepkey plugin |
|||
self.set_enabled(False) |
|||
return wallet |
|||
|
|||
@hook |
|||
def receive_menu(self, menu, addrs): |
|||
if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1: |
|||
menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0])) |
|||
|
|||
def settings_dialog(self, window): |
|||
try: |
|||
device_id = self.get_client().get_device_id() |
|||
except BaseException as e: |
|||
window.show_message(str(e)) |
|||
return |
|||
get_label = lambda: self.get_client().features.label |
|||
update_label = lambda: current_label_label.setText("Label: %s" % get_label()) |
|||
d = QDialog() |
|||
layout = QGridLayout(d) |
|||
layout.addWidget(QLabel("KeepKey Options"),0,0) |
|||
layout.addWidget(QLabel("ID:"),1,0) |
|||
layout.addWidget(QLabel(" %s" % device_id),1,1) |
|||
|
|||
def modify_label(): |
|||
response = QInputDialog().getText(None, "Set New KeepKey Label", "New KeepKey Label: (upon submission confirm on KeepKey)") |
|||
if not response[1]: |
|||
return |
|||
new_label = str(response[0]) |
|||
self.handler.show_message("Please confirm label change on KeepKey") |
|||
status = self.get_client().apply_settings(label=new_label) |
|||
self.handler.stop() |
|||
update_label() |
|||
|
|||
current_label_label = QLabel() |
|||
update_label() |
|||
change_label_button = QPushButton("Modify") |
|||
change_label_button.clicked.connect(modify_label) |
|||
layout.addWidget(current_label_label,3,0) |
|||
layout.addWidget(change_label_button,3,1) |
|||
d.exec_() |
|||
|
@ -0,0 +1,9 @@ |
|||
from electrum.i18n import _ |
|||
|
|||
fullname = _('LabelSync') |
|||
description = '\n'.join([ |
|||
_("Synchronize your labels across multiple Electrum installs by using a remote database to save your data. Labels, transactions ids and addresses are encrypted before they are sent to the remote server."), |
|||
_("The label sync's server software is open-source as well and can be found on github.com/maran/electrum-sync-server") |
|||
]) |
|||
available_for = ['qt', 'kivy'] |
|||
|
@ -0,0 +1,3 @@ |
|||
from labels import LabelsPlugin |
|||
class Plugin(LabelsPlugin): |
|||
pass |
@ -0,0 +1,80 @@ |
|||
import hashlib |
|||
import threading |
|||
from functools import partial |
|||
|
|||
from PyQt4.QtGui import * |
|||
from PyQt4.QtCore import * |
|||
import PyQt4.QtCore as QtCore |
|||
import PyQt4.QtGui as QtGui |
|||
|
|||
from electrum.plugins import hook |
|||
from electrum.i18n import _ |
|||
from electrum_gui.qt import HelpButton, EnterButton |
|||
from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton |
|||
|
|||
from labels import LabelsPlugin |
|||
|
|||
|
|||
class Plugin(LabelsPlugin): |
|||
|
|||
def __init__(self, *args): |
|||
LabelsPlugin.__init__(self, *args) |
|||
self.obj = QObject() |
|||
|
|||
def requires_settings(self): |
|||
return True |
|||
|
|||
def settings_widget(self, window): |
|||
return EnterButton(_('Settings'), |
|||
partial(self.settings_dialog, window)) |
|||
|
|||
def settings_dialog(self, window): |
|||
d = QDialog(window) |
|||
vbox = QVBoxLayout(d) |
|||
layout = QGridLayout() |
|||
vbox.addLayout(layout) |
|||
layout.addWidget(QLabel("Label sync options: "), 2, 0) |
|||
self.upload = ThreadedButton("Force upload", |
|||
partial(self.push_thread, window.wallet), |
|||
self.done_processing) |
|||
layout.addWidget(self.upload, 2, 1) |
|||
self.download = ThreadedButton("Force download", |
|||
partial(self.pull_thread, window.wallet, True), |
|||
self.done_processing) |
|||
layout.addWidget(self.download, 2, 2) |
|||
self.accept = OkButton(d, _("Done")) |
|||
vbox.addLayout(Buttons(CancelButton(d), self.accept)) |
|||
if d.exec_(): |
|||
return True |
|||
else: |
|||
return False |
|||
|
|||
def on_pulled(self, wallet): |
|||
self.obj.emit(SIGNAL('labels_changed'), wallet) |
|||
|
|||
def done_processing(self): |
|||
QMessageBox.information(None, _("Labels synchronised"), |
|||
_("Your labels have been synchronised.")) |
|||
|
|||
@hook |
|||
def on_new_window(self, window): |
|||
window.connect(window.app, SIGNAL('labels_changed'), window.update_tabs) |
|||
wallet = window.wallet |
|||
nonce = self.get_nonce(wallet) |
|||
self.print_error("wallet", wallet.basename(), "nonce is", nonce) |
|||
mpk = ''.join(sorted(wallet.get_master_public_keys().values())) |
|||
if not mpk: |
|||
return |
|||
password = hashlib.sha1(mpk).digest().encode('hex')[:32] |
|||
iv = hashlib.sha256(password).digest()[:16] |
|||
wallet_id = hashlib.sha256(mpk).digest().encode('hex') |
|||
self.wallets[wallet] = (password, iv, wallet_id) |
|||
# If there is an auth token we can try to actually start syncing |
|||
t = threading.Thread(target=self.pull_thread, args=(wallet, False)) |
|||
t.setDaemon(True) |
|||
t.start() |
|||
|
|||
@hook |
|||
def on_close_window(self, window): |
|||
self.wallets.pop(window.wallet) |
|||
|
@ -0,0 +1,8 @@ |
|||
from electrum.i18n import _ |
|||
|
|||
fullname = 'Ledger Wallet' |
|||
description = 'Provides support for Ledger hardware wallet' |
|||
requires = [('btchip', 'github.com/ledgerhq/btchip-python')] |
|||
requires_wallet_type = ['btchip'] |
|||
registers_wallet_type = ('hardware', 'btchip', _("Ledger wallet")) |
|||
available_for = ['qt', 'cmdline'] |
@ -0,0 +1,66 @@ |
|||
from PyQt4.Qt import QApplication, QMessageBox, QDialog, QInputDialog, QLineEdit, QVBoxLayout, QLabel, QThread, SIGNAL |
|||
import PyQt4.QtCore as QtCore |
|||
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog |
|||
|
|||
class Plugin(LedgerPlugin): |
|||
|
|||
@hook |
|||
def load_wallet(self, wallet, window): |
|||
self.wallet = wallet |
|||
self.wallet.plugin = self |
|||
if self.handler is None: |
|||
self.handler = BTChipQTHandler(window) |
|||
if self.btchip_is_connected(): |
|||
if not self.wallet.check_proper_device(): |
|||
QMessageBox.information(window, _('Error'), _("This wallet does not match your Ledger device"), _('OK')) |
|||
self.wallet.force_watching_only = True |
|||
else: |
|||
QMessageBox.information(window, _('Error'), _("Ledger device not detected.\nContinuing in watching-only mode."), _('OK')) |
|||
self.wallet.force_watching_only = True |
|||
|
|||
|
|||
class BTChipQTHandler: |
|||
|
|||
def __init__(self, win): |
|||
self.win = win |
|||
self.win.connect(win, SIGNAL('btchip_done'), self.dialog_stop) |
|||
self.win.connect(win, SIGNAL('btchip_message_dialog'), self.message_dialog) |
|||
self.win.connect(win, SIGNAL('btchip_auth_dialog'), self.auth_dialog) |
|||
self.done = threading.Event() |
|||
|
|||
def stop(self): |
|||
self.win.emit(SIGNAL('btchip_done')) |
|||
|
|||
def show_message(self, msg): |
|||
self.message = msg |
|||
self.win.emit(SIGNAL('btchip_message_dialog')) |
|||
|
|||
def prompt_auth(self, msg): |
|||
self.done.clear() |
|||
self.message = msg |
|||
self.win.emit(SIGNAL('btchip_auth_dialog')) |
|||
self.done.wait() |
|||
return self.response |
|||
|
|||
def auth_dialog(self): |
|||
response = QInputDialog.getText(None, "Ledger Wallet Authentication", self.message, QLineEdit.Password) |
|||
if not response[1]: |
|||
self.response = None |
|||
else: |
|||
self.response = str(response[0]) |
|||
self.done.set() |
|||
|
|||
def message_dialog(self): |
|||
self.d = QDialog() |
|||
self.d.setModal(1) |
|||
self.d.setWindowTitle('Ledger') |
|||
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) |
|||
l = QLabel(self.message) |
|||
vbox = QVBoxLayout(self.d) |
|||
vbox.addWidget(l) |
|||
self.d.show() |
|||
|
|||
def dialog_stop(self): |
|||
if self.d is not None: |
|||
self.d.hide() |
|||
self.d = None |
@ -0,0 +1,6 @@ |
|||
from electrum.i18n import _ |
|||
|
|||
fullname = 'Plot History' |
|||
description = _("Ability to plot transaction history in graphical mode.") |
|||
requires = [('matplotlib', 'matplotlib')] |
|||
available_for = ['qt'] |
@ -0,0 +1,9 @@ |
|||
from electrum.i18n import _ |
|||
|
|||
fullname = 'Trezor Wallet' |
|||
description = _('Provides support for Trezor hardware wallet') |
|||
requires = [('trezorlib','github.com/trezor/python-trezor')] |
|||
requires_wallet_type = ['trezor'] |
|||
registers_wallet_type = ('hardware', 'trezor', _("Trezor wallet")) |
|||
available_for = ['qt', 'cmdline'] |
|||
|
@ -0,0 +1,200 @@ |
|||
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton |
|||
import PyQt4.QtCore as QtCore |
|||
from electrum_gui.qt.util import * |
|||
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow |
|||
from electrum_gui.qt.installwizard import InstallWizard |
|||
from trezorlib.qt.pinmatrix import PinMatrixWidget |
|||
|
|||
|
|||
from functools import partial |
|||
import unicodedata |
|||
|
|||
from electrum.i18n import _ |
|||
from electrum.plugins import hook, always_hook, run_hook |
|||
|
|||
from trezor import TrezorPlugin |
|||
|
|||
class TrezorQtHandler: |
|||
|
|||
def __init__(self, win): |
|||
self.win = win |
|||
self.win.connect(win, SIGNAL('trezor_done'), self.dialog_stop) |
|||
self.win.connect(win, SIGNAL('message_dialog'), self.message_dialog) |
|||
self.win.connect(win, SIGNAL('pin_dialog'), self.pin_dialog) |
|||
self.win.connect(win, SIGNAL('passphrase_dialog'), self.passphrase_dialog) |
|||
self.done = threading.Event() |
|||
|
|||
def stop(self): |
|||
self.win.emit(SIGNAL('trezor_done')) |
|||
|
|||
def show_message(self, msg): |
|||
self.message = msg |
|||
self.win.emit(SIGNAL('message_dialog')) |
|||
|
|||
def get_pin(self, msg): |
|||
self.done.clear() |
|||
self.message = msg |
|||
self.win.emit(SIGNAL('pin_dialog')) |
|||
self.done.wait() |
|||
return self.response |
|||
|
|||
def get_passphrase(self, msg): |
|||
self.done.clear() |
|||
self.message = msg |
|||
self.win.emit(SIGNAL('passphrase_dialog')) |
|||
self.done.wait() |
|||
return self.passphrase |
|||
|
|||
def pin_dialog(self): |
|||
d = QDialog(None) |
|||
d.setModal(1) |
|||
d.setWindowTitle(_("Enter PIN")) |
|||
d.setWindowFlags(d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) |
|||
matrix = PinMatrixWidget() |
|||
vbox = QVBoxLayout() |
|||
vbox.addWidget(QLabel(self.message)) |
|||
vbox.addWidget(matrix) |
|||
vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) |
|||
d.setLayout(vbox) |
|||
if not d.exec_(): |
|||
self.response = None |
|||
self.response = str(matrix.get_value()) |
|||
self.done.set() |
|||
|
|||
def passphrase_dialog(self): |
|||
if type(self.win) is ElectrumWindow: |
|||
passphrase = self.win.password_dialog(_("Please enter your Trezor passphrase")) |
|||
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else '' |
|||
else: |
|||
assert type(self.win) is InstallWizard |
|||
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog |
|||
d = QDialog() |
|||
d.setModal(1) |
|||
d.setLayout(make_password_dialog(d, None, self.message, False)) |
|||
confirmed, p, passphrase = run_password_dialog(d, None, None) |
|||
if not confirmed: |
|||
QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK')) |
|||
self.passphrase = None |
|||
else: |
|||
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else '' |
|||
self.done.set() |
|||
|
|||
def message_dialog(self): |
|||
self.d = QDialog() |
|||
self.d.setModal(1) |
|||
self.d.setWindowTitle('Please Check Trezor Device') |
|||
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) |
|||
l = QLabel(self.message) |
|||
vbox = QVBoxLayout(self.d) |
|||
vbox.addWidget(l) |
|||
self.d.show() |
|||
|
|||
def dialog_stop(self): |
|||
self.d.hide() |
|||
|
|||
|
|||
class Plugin(TrezorPlugin): |
|||
|
|||
@hook |
|||
def load_wallet(self, wallet, window): |
|||
self.print_error("load_wallet") |
|||
self.wallet = wallet |
|||
self.wallet.plugin = self |
|||
self.trezor_button = StatusBarButton(QIcon(":icons/trezor.png"), _("Trezor"), partial(self.settings_dialog, window)) |
|||
if type(window) is ElectrumWindow: |
|||
window.statusBar().addPermanentWidget(self.trezor_button) |
|||
if self.handler is None: |
|||
self.handler = TrezorQtHandler(window) |
|||
try: |
|||
self.get_client().ping('t') |
|||
except BaseException as e: |
|||
QMessageBox.information(window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK')) |
|||
self.wallet.force_watching_only = True |
|||
return |
|||
if self.wallet.addresses() and not self.wallet.check_proper_device(): |
|||
QMessageBox.information(window, _('Error'), _("This wallet does not match your Trezor device"), _('OK')) |
|||
self.wallet.force_watching_only = True |
|||
|
|||
@hook |
|||
def installwizard_load_wallet(self, wallet, window): |
|||
if type(wallet) != TrezorWallet: |
|||
return |
|||
self.load_wallet(wallet, window) |
|||
|
|||
@hook |
|||
def installwizard_restore(self, wizard, storage): |
|||
if storage.get('wallet_type') != 'trezor': |
|||
return |
|||
seed = wizard.enter_seed_dialog("Enter your Trezor seed", None, func=lambda x:True) |
|||
if not seed: |
|||
return |
|||
wallet = TrezorWallet(storage) |
|||
self.wallet = wallet |
|||
handler = TrezorQtHandler(wizard) |
|||
passphrase = handler.get_passphrase(_("Please enter your Trezor passphrase.") + '\n' + _("Press OK if you do not use one.")) |
|||
if passphrase is None: |
|||
return |
|||
password = wizard.password_dialog() |
|||
wallet.add_seed(seed, password) |
|||
wallet.add_cosigner_seed(seed, 'x/', password, passphrase) |
|||
wallet.create_main_account(password) |
|||
# disable trezor plugin |
|||
self.set_enabled(False) |
|||
return wallet |
|||
|
|||
@hook |
|||
def receive_menu(self, menu, addrs): |
|||
if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1: |
|||
menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0])) |
|||
|
|||
def show_address(self, address): |
|||
if not self.wallet.check_proper_device(): |
|||
give_error('Wrong device or password') |
|||
try: |
|||
address_path = self.wallet.address_id(address) |
|||
address_n = self.get_client().expand_path(address_path) |
|||
except Exception, e: |
|||
give_error(e) |
|||
try: |
|||
self.get_client().get_address('Bitcoin', address_n, True) |
|||
except Exception, e: |
|||
give_error(e) |
|||
finally: |
|||
self.handler.stop() |
|||
|
|||
|
|||
def settings_dialog(self, window): |
|||
try: |
|||
device_id = self.get_client().get_device_id() |
|||
except BaseException as e: |
|||
window.show_message(str(e)) |
|||
return |
|||
get_label = lambda: self.get_client().features.label |
|||
update_label = lambda: current_label_label.setText("Label: %s" % get_label()) |
|||
d = QDialog() |
|||
layout = QGridLayout(d) |
|||
layout.addWidget(QLabel("Trezor Options"),0,0) |
|||
layout.addWidget(QLabel("ID:"),1,0) |
|||
layout.addWidget(QLabel(" %s" % device_id),1,1) |
|||
|
|||
def modify_label(): |
|||
response = QInputDialog().getText(None, "Set New Trezor Label", "New Trezor Label: (upon submission confirm on Trezor)") |
|||
if not response[1]: |
|||
return |
|||
new_label = str(response[0]) |
|||
self.handler.show_message("Please confirm label change on Trezor") |
|||
status = self.get_client().apply_settings(label=new_label) |
|||
self.handler.stop() |
|||
update_label() |
|||
|
|||
current_label_label = QLabel() |
|||
update_label() |
|||
change_label_button = QPushButton("Modify") |
|||
change_label_button.clicked.connect(modify_label) |
|||
layout.addWidget(current_label_label,3,0) |
|||
layout.addWidget(change_label_button,3,1) |
|||
d.exec_() |
|||
|
|||
|
|||
|
|||
|
@ -0,0 +1,11 @@ |
|||
from electrum.i18n import _ |
|||
|
|||
fullname = _('Two Factor Authentication') |
|||
description = ''.join([ |
|||
_("This plugin adds two-factor authentication to your wallet."), '<br/>', |
|||
_("For more information, visit"), |
|||
" <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>" |
|||
]) |
|||
requires_wallet_type = ['2fa'] |
|||
registers_wallet_type = ('twofactor', '2fa', _("Wallet with two-factor authentication")) |
|||
available_for = ['qt', 'cmdline'] |
@ -0,0 +1,225 @@ |
|||
from PyQt4.QtGui import * |
|||
from PyQt4.QtCore import * |
|||
from electrum_gui.qt.util import * |
|||
from electrum_gui.qt.qrcodewidget import QRCodeWidget |
|||
from electrum_gui.qt.amountedit import AmountEdit |
|||
from electrum_gui.qt.main_window import StatusBarButton |
|||
|
|||
class Plugin(TrustedCoinPlugin): |
|||
|
|||
def auth_dialog(self, window): |
|||
d = QDialog(window) |
|||
d.setModal(1) |
|||
vbox = QVBoxLayout(d) |
|||
pw = AmountEdit(None, is_int = True) |
|||
msg = _('Please enter your Google Authenticator code') |
|||
vbox.addWidget(QLabel(msg)) |
|||
grid = QGridLayout() |
|||
grid.setSpacing(8) |
|||
grid.addWidget(QLabel(_('Code')), 1, 0) |
|||
grid.addWidget(pw, 1, 1) |
|||
vbox.addLayout(grid) |
|||
vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) |
|||
if not d.exec_(): |
|||
return |
|||
return pw.get_amount() |
|||
|
|||
@hook |
|||
def sign_tx(self, window, tx): |
|||
self.print_error("twofactor:sign_tx") |
|||
wallet = window.wallet |
|||
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server(): |
|||
auth_code = None |
|||
if need_server(wallet, tx): |
|||
auth_code = self.auth_dialog(window) |
|||
else: |
|||
self.print_error("twofactor: xpub3 not needed") |
|||
window.wallet.auth_code = auth_code |
|||
|
|||
@hook |
|||
def abort_send(self, window): |
|||
wallet = window.wallet |
|||
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server(): |
|||
if wallet.billing_info is None: |
|||
# request billing info before forming the transaction |
|||
task = partial(self.request_billing_info, wallet) |
|||
waiting_dialog = WaitingDialog(window, 'please wait...', task) |
|||
waiting_dialog.start() |
|||
waiting_dialog.wait() |
|||
if wallet.billing_info is None: |
|||
window.show_message('Could not contact server') |
|||
return True |
|||
return False |
|||
|
|||
|
|||
def settings_dialog(self, window): |
|||
task = partial(self.request_billing_info, window.wallet) |
|||
self.waiting_dialog = WaitingDialog(window, 'please wait...', task, partial(self.show_settings_dialog, window)) |
|||
self.waiting_dialog.start() |
|||
|
|||
def show_settings_dialog(self, window, success): |
|||
if not success: |
|||
window.show_message(_('Server not reachable.')) |
|||
return |
|||
|
|||
wallet = window.wallet |
|||
d = QDialog(window) |
|||
d.setWindowTitle("TrustedCoin Information") |
|||
d.setMinimumSize(500, 200) |
|||
vbox = QVBoxLayout(d) |
|||
hbox = QHBoxLayout() |
|||
|
|||
logo = QLabel() |
|||
logo.setPixmap(QPixmap(":icons/trustedcoin.png")) |
|||
msg = _('This wallet is protected by TrustedCoin\'s two-factor authentication.') + '<br/>'\ |
|||
+ _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>" |
|||
label = QLabel(msg) |
|||
label.setOpenExternalLinks(1) |
|||
|
|||
hbox.addStretch(10) |
|||
hbox.addWidget(logo) |
|||
hbox.addStretch(10) |
|||
hbox.addWidget(label) |
|||
hbox.addStretch(10) |
|||
|
|||
vbox.addLayout(hbox) |
|||
vbox.addStretch(10) |
|||
|
|||
msg = _('TrustedCoin charges a fee per co-signed transaction. You may pay on each transaction (an extra output will be added to your transaction), or you may purchase prepaid transaction using this dialog.') + '<br/>' |
|||
label = QLabel(msg) |
|||
label.setWordWrap(1) |
|||
vbox.addWidget(label) |
|||
|
|||
vbox.addStretch(10) |
|||
grid = QGridLayout() |
|||
vbox.addLayout(grid) |
|||
|
|||
price_per_tx = wallet.price_per_tx |
|||
v = price_per_tx.get(1) |
|||
grid.addWidget(QLabel(_("Price per transaction (not prepaid):")), 0, 0) |
|||
grid.addWidget(QLabel(window.format_amount(v) + ' ' + window.base_unit()), 0, 1) |
|||
|
|||
i = 1 |
|||
|
|||
if 10 not in price_per_tx: |
|||
price_per_tx[10] = 10 * price_per_tx.get(1) |
|||
|
|||
for k, v in sorted(price_per_tx.items()): |
|||
if k == 1: |
|||
continue |
|||
grid.addWidget(QLabel("Price for %d prepaid transactions:"%k), i, 0) |
|||
grid.addWidget(QLabel("%d x "%k + window.format_amount(v/k) + ' ' + window.base_unit()), i, 1) |
|||
b = QPushButton(_("Buy")) |
|||
b.clicked.connect(lambda b, k=k, v=v: self.on_buy(window, k, v, d)) |
|||
grid.addWidget(b, i, 2) |
|||
i += 1 |
|||
|
|||
n = wallet.billing_info.get('tx_remaining', 0) |
|||
grid.addWidget(QLabel(_("Your wallet has %d prepaid transactions.")%n), i, 0) |
|||
|
|||
# tranfer button |
|||
#def on_transfer(): |
|||
# server.transfer_credit(self.user_id, recipient, otp, signature_callback) |
|||
# pass |
|||
#b = QPushButton(_("Transfer")) |
|||
#b.clicked.connect(on_transfer) |
|||
#grid.addWidget(b, 1, 2) |
|||
|
|||
#grid.addWidget(QLabel(_("Next Billing Address:")), i, 0) |
|||
#grid.addWidget(QLabel(self.billing_info['billing_address']), i, 1) |
|||
vbox.addLayout(Buttons(CloseButton(d))) |
|||
d.exec_() |
|||
|
|||
def on_buy(self, window, k, v, d): |
|||
d.close() |
|||
if window.pluginsdialog: |
|||
window.pluginsdialog.close() |
|||
wallet = window.wallet |
|||
uri = "bitcoin:" + wallet.billing_info['billing_address'] + "?message=TrustedCoin %d Prepaid Transactions&amount="%k + str(Decimal(v)/100000000) |
|||
wallet.is_billing = True |
|||
window.pay_to_URI(uri) |
|||
window.payto_e.setFrozen(True) |
|||
window.message_e.setFrozen(True) |
|||
window.amount_e.setFrozen(True) |
|||
|
|||
def accept_terms_of_use(self, window): |
|||
vbox = QVBoxLayout() |
|||
window.set_layout(vbox) |
|||
vbox.addWidget(QLabel(_("Terms of Service"))) |
|||
|
|||
tos_e = QTextEdit() |
|||
tos_e.setReadOnly(True) |
|||
vbox.addWidget(tos_e) |
|||
|
|||
vbox.addWidget(QLabel(_("Please enter your e-mail address"))) |
|||
email_e = QLineEdit() |
|||
vbox.addWidget(email_e) |
|||
vbox.addStretch() |
|||
accept_button = OkButton(window, _('Accept')) |
|||
accept_button.setEnabled(False) |
|||
vbox.addLayout(Buttons(CancelButton(window), accept_button)) |
|||
|
|||
def request_TOS(): |
|||
tos = server.get_terms_of_service() |
|||
self.TOS = tos |
|||
window.emit(SIGNAL('twofactor:TOS')) |
|||
|
|||
def on_result(): |
|||
tos_e.setText(self.TOS) |
|||
|
|||
window.connect(window, SIGNAL('twofactor:TOS'), on_result) |
|||
t = Thread(target=request_TOS) |
|||
t.setDaemon(True) |
|||
t.start() |
|||
|
|||
regexp = r"[^@]+@[^@]+\.[^@]+" |
|||
email_e.textChanged.connect(lambda: accept_button.setEnabled(re.match(regexp,email_e.text()) is not None)) |
|||
email_e.setFocus(True) |
|||
|
|||
if not window.exec_(): |
|||
return |
|||
|
|||
email = str(email_e.text()) |
|||
return email |
|||
|
|||
|
|||
def setup_google_auth(self, window, _id, otp_secret): |
|||
vbox = QVBoxLayout() |
|||
window.set_layout(vbox) |
|||
if otp_secret is not None: |
|||
uri = "otpauth://totp/%s?secret=%s"%('trustedcoin.com', otp_secret) |
|||
vbox.addWidget(QLabel("Please scan this QR code in Google Authenticator.")) |
|||
qrw = QRCodeWidget(uri) |
|||
vbox.addWidget(qrw, 1) |
|||
msg = _('Then, enter your Google Authenticator code:') |
|||
else: |
|||
label = QLabel("This wallet is already registered, but it was never authenticated. To finalize your registration, please enter your Google Authenticator Code. If you do not have this code, delete the wallet file and start a new registration") |
|||
label.setWordWrap(1) |
|||
vbox.addWidget(label) |
|||
msg = _('Google Authenticator code:') |
|||
|
|||
hbox = QHBoxLayout() |
|||
hbox.addWidget(QLabel(msg)) |
|||
pw = AmountEdit(None, is_int = True) |
|||
pw.setFocus(True) |
|||
hbox.addWidget(pw) |
|||
hbox.addStretch(1) |
|||
vbox.addLayout(hbox) |
|||
|
|||
b = OkButton(window, _('Next')) |
|||
b.setEnabled(False) |
|||
vbox.addLayout(Buttons(CancelButton(window), b)) |
|||
pw.textChanged.connect(lambda: b.setEnabled(len(pw.text())==6)) |
|||
|
|||
while True: |
|||
if not window.exec_(): |
|||
return False |
|||
otp = pw.get_amount() |
|||
try: |
|||
server.auth(_id, otp) |
|||
return True |
|||
except: |
|||
QMessageBox.information(window, _('Message'), _('Incorrect password'), _('OK')) |
|||
pw.setText('') |
|||
|
|||
|
@ -0,0 +1,5 @@ |
|||
from electrum.i18n import _ |
|||
|
|||
fullname = 'Virtual Keyboard' |
|||
description = '%s\n%s' % (_("Add an optional virtual keyboard to the password dialog."), _("Warning: do not use this if it makes you pick a weaker password.")) |
|||
available_for = ['qt'] |
Loading…
Reference in new issue