Beispiel #1
0
 def passphrase_dialog(self, msg, confirm):
     # If confirm is true, require the user to enter the passphrase twice
     parent = self.top_level_window()
     d = WindowModalDialog(parent, _("Enter Passphrase"))
     if confirm:
         OK_button = OkButton(d)
         playout = PasswordLayout(msg=msg,
                                  kind=PW_PASSPHRASE,
                                  OK_button=OK_button)
         vbox = QVBoxLayout()
         vbox.addLayout(playout.layout())
         vbox.addLayout(Buttons(CancelButton(d), OK_button))
         d.setLayout(vbox)
         passphrase = playout.new_password() if d.exec_() else None
     else:
         pw = PasswordLineEdit()
         pw.setMinimumWidth(200)
         vbox = QVBoxLayout()
         vbox.addWidget(WWLabel(msg))
         vbox.addWidget(pw)
         vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
         d.setLayout(vbox)
         passphrase = pw.text() if d.exec_() else None
     self.passphrase = passphrase
     self.done.set()
Beispiel #2
0
    def __init__(self, handler, data, *, client: 'Ledger_Client'):
        '''Ask user for 2nd factor authentication. Support text and security card methods.
        Use last method from settings, but support downgrade.
        '''
        QDialog.__init__(self, handler.top_level_window())
        self.handler = handler
        self.txdata = data
        self.idxs = self.txdata[
            'keycardData'] if self.txdata['confirmationType'] > 1 else ''
        self.setMinimumWidth(650)
        self.setWindowTitle(_("Ledger Wallet Authentication"))
        self.cfg = copy.deepcopy(self.handler.win.wallet.get_keystore().cfg)
        self.dongle = client.dongleObject.dongle
        self.pin = ''

        self.devmode = self.getDevice2FAMode()
        if self.devmode == 0x11 or self.txdata['confirmationType'] == 1:
            self.cfg['mode'] = 0

        vbox = QVBoxLayout()
        self.setLayout(vbox)

        def on_change_mode(idx):
            self.cfg[
                'mode'] = 0 if self.devmode == 0x11 else idx if idx > 0 else 1
            if self.cfg['mode'] > 0:
                self.handler.win.wallet.get_keystore().cfg = self.cfg
                self.handler.win.wallet.save_keystore()
            self.update_dlg()

        def return_pin():
            self.pin = self.pintxt.text(
            ) if self.txdata['confirmationType'] == 1 else self.cardtxt.text()
            if self.cfg['mode'] == 1:
                self.pin = ''.join(chr(int(str(i), 16)) for i in self.pin)
            self.accept()

        self.modebox = QWidget()
        modelayout = QHBoxLayout()
        self.modebox.setLayout(modelayout)
        modelayout.addWidget(QLabel(_("Method:")))
        self.modes = QComboBox()
        modelayout.addWidget(self.modes, 2)
        modelayout.addStretch(1)
        self.modebox.setMaximumHeight(50)
        vbox.addWidget(self.modebox)

        self.populate_modes()
        self.modes.currentIndexChanged.connect(on_change_mode)

        self.helpmsg = QTextEdit()
        self.helpmsg.setStyleSheet(
            "QTextEdit { color:black; background-color: lightgray; }")
        self.helpmsg.setReadOnly(True)
        vbox.addWidget(self.helpmsg)

        self.pinbox = QWidget()
        pinlayout = QHBoxLayout()
        self.pinbox.setLayout(pinlayout)
        self.pintxt = PasswordLineEdit()
        self.pintxt.setMaxLength(4)
        self.pintxt.returnPressed.connect(return_pin)
        pinlayout.addWidget(QLabel(_("Enter PIN:")))
        pinlayout.addWidget(self.pintxt)
        pinlayout.addWidget(QLabel(_("NOT DEVICE PIN - see above")))
        pinlayout.addStretch(1)
        self.pinbox.setVisible(self.cfg['mode'] == 0)
        vbox.addWidget(self.pinbox)

        self.cardbox = QWidget()
        card = QVBoxLayout()
        self.cardbox.setLayout(card)
        self.addrtext = QTextEdit()
        self.addrtext.setStyleSheet('''
            QTextEdit {
                color:blue; background-color:lightgray; padding:15px 10px; border:none;
                font-size:20pt; font-family: "Courier New", monospace; }
        ''')
        self.addrtext.setReadOnly(True)
        self.addrtext.setMaximumHeight(130)
        card.addWidget(self.addrtext)

        def pin_changed(s):
            if len(s) < len(self.idxs):
                i = self.idxs[len(s)]
                addr = self.txdata['address']
                if not constants.net.TESTNET:
                    text = addr[:i] + '<u><b>' + addr[
                        i:i + 1] + '</u></b>' + addr[i + 1:]
                else:
                    # pin needs to be created from mainnet address
                    addr_mainnet = bitcoin.script_to_address(
                        bitcoin.address_to_script(addr),
                        net=constants.QtumMainnet)
                    addr_mainnet = addr_mainnet[:i] + '<u><b>' + addr_mainnet[
                        i:i + 1] + '</u></b>' + addr_mainnet[i + 1:]
                    text = str(addr) + '\n' + str(addr_mainnet)
                self.addrtext.setHtml(str(text))
            else:
                self.addrtext.setHtml(_("Press Enter"))

        pin_changed('')
        cardpin = QHBoxLayout()
        cardpin.addWidget(QLabel(_("Enter PIN:")))
        self.cardtxt = PasswordLineEdit()
        self.cardtxt.setMaxLength(len(self.idxs))
        self.cardtxt.textChanged.connect(pin_changed)
        self.cardtxt.returnPressed.connect(return_pin)
        cardpin.addWidget(self.cardtxt)
        cardpin.addWidget(QLabel(_("NOT DEVICE PIN - see above")))
        cardpin.addStretch(1)
        card.addLayout(cardpin)
        self.cardbox.setVisible(self.cfg['mode'] == 1)
        vbox.addWidget(self.cardbox)

        self.update_dlg()
Beispiel #3
0
    def passphrase_dialog(self, msg, confirm):
        # If confirm is true, require the user to enter the passphrase twice
        parent = self.top_level_window()
        d = WindowModalDialog(parent, _('Enter Passphrase'))

        OK_button = OkButton(d, _('Enter Passphrase'))
        OnDevice_button = QPushButton(_('Enter Passphrase on Device'))

        new_pw = PasswordLineEdit()
        conf_pw = PasswordLineEdit()

        vbox = QVBoxLayout()
        label = QLabel(msg + "\n")
        label.setWordWrap(True)

        grid = QGridLayout()
        grid.setSpacing(8)
        grid.setColumnMinimumWidth(0, 150)
        grid.setColumnMinimumWidth(1, 100)
        grid.setColumnStretch(1,1)

        vbox.addWidget(label)

        grid.addWidget(QLabel(_('Passphrase:')), 0, 0)
        grid.addWidget(new_pw, 0, 1)

        if confirm:
            grid.addWidget(QLabel(_('Confirm Passphrase:')), 1, 0)
            grid.addWidget(conf_pw, 1, 1)

        vbox.addLayout(grid)

        def enable_OK():
            if not confirm:
                ok = True
            else:
                ok = new_pw.text() == conf_pw.text()
            OK_button.setEnabled(ok)

        new_pw.textChanged.connect(enable_OK)
        conf_pw.textChanged.connect(enable_OK)

        vbox.addWidget(OK_button)

        if self.passphrase_on_device:
            vbox.addWidget(OnDevice_button)

        d.setLayout(vbox)

        self.passphrase = None

        def ok_clicked():
            self.passphrase = new_pw.text()

        def on_device_clicked():
            self.passphrase = PASSPHRASE_ON_DEVICE

        OK_button.clicked.connect(ok_clicked)
        OnDevice_button.clicked.connect(on_device_clicked)
        OnDevice_button.clicked.connect(d.accept)

        d.exec_()
        self.done.set()
    def __init__(self, *, window: 'ElectrumWindow', txs, password, is_sweep):

        WindowModalDialog.__init__(self, window,
                                   _('BitPost Transactions Preview'))
        self.setMinimumSize(800, 600)
        self.main_window = window
        self.txs = txs
        self.password_required = self.main_window.wallet.has_keystore_encryption(
        ) and not is_sweep
        self.is_send = False
        vbox = QVBoxLayout()
        self.setLayout(vbox)
        lbox = QListWidget()

        items = []
        for tx in txs:
            inputs = tx.inputs()
            outputs = tx.outputs()
            fee = tx.get_fee()
            fiat = False

            text = "fee: {}".format(fee)
            if self.main_window.fx and self.main_window.fx.is_enabled():
                fiat = Exchange(self.main_window.fx)
                text += fiat.str_exchange(fee)

            text += "\t\tvbyte: {}\n".format(tx.estimated_size())

            tmp = "fee/vbyte: {}".format(round(fee / tx.estimated_size(), 2))
            if fiat:
                tmp += fiat.str_exchange(fee / tx.estimated_size())
            text += tmp
            if len(tmp) > 22:
                tab = "\t"
            else:
                tab = "\t\t"

            text += "{}total size: {}\n".format(tab, tx.estimated_total_size())

            text += "INPUTS:\n"
            for i in inputs:
                text += "{}:{} = {}".format(i.prevout.txid.hex(),
                                            i.prevout.out_idx, i.value_sats())
                if fiat:
                    text += fiat.str_exchange(i.value_sats())
                text += "\n"
            text += "OUTPUTS:\n"
            for o in outputs:
                text += "{} = {}".format(o.address, o.value)
                if fiat:
                    text += fiat.str_exchange(o.value)
                text += "\n"

            items.append(text)

        lbox.addItems(items)
        vbox.addWidget(lbox)

        self.send_button = QPushButton(_('Send'))
        self.send_button.clicked.connect(self.on_send)
        self.send_button.setDefault(True)

        self.pw_label = QLabel(_('Password'))
        self.pw_label.setVisible(self.password_required)
        self.pw = PasswordLineEdit(password)
        self.pw.setVisible(self.password_required)

        vbox.addLayout(
            Buttons(CancelButton(self), self.pw_label, self.pw,
                    self.send_button))
Beispiel #5
0
    def build_gui(self):
        vbox = QVBoxLayout()
        self.setLayout(vbox)
        grid = QGridLayout()
        vbox.addLayout(grid)
        self.amount_label = QLabel('')

        grid.addWidget(QLabel(_("Target for confirmation")), 0, 0)
        self.qtarget = QDateTimeEdit(QDateTime.currentDateTime().addSecs(
            int(self.main_window.config.get('bitpost_target_interval')) * 60))
        grid.addWidget(self.qtarget, 0, 1)

        self.asap_check = QCheckBox("ASAP")
        self.asap_check.clicked.connect(self.toggle_target)
        grid.addWidget(self.asap_check, 0, 2)

        grid.addWidget(QLabel(_("Maximum Fee")), 2, 0)
        self.max_fees = QLineEdit(
            str(self.main_window.config.get('bitpost_max_fee')))
        self.max_fees.textChanged.connect(self.change_max_fees)
        grid.addWidget(self.max_fees, 2, 1)
        self.fee_combo = QComboBox()
        fee_combo_values = get_fee_units(
            self.main_window,
            self.main_window.config.get('bitpost_max_fee_unit'))
        self.fee_combo.addItems(fee_combo_values)
        grid.addWidget(self.fee_combo, 2, 2)

        self.schedule_check = QCheckBox(_("Schedule transaction"))
        self.schedule_check.clicked.connect(self.toggle_delay)
        grid.addWidget(self.schedule_check, 3, 0, 1, -1)
        self.qdelay = QDateTimeEdit(QDateTime.currentDateTime())
        grid.addWidget(self.qdelay, 4, 0)
        sp_retain = QSizePolicy(self.qdelay.sizePolicy())
        sp_retain.setRetainSizeWhenHidden(True)
        self.qdelay.setSizePolicy(sp_retain)
        self.qdelay.setVisible(False)

        self.message_label = QLabel(self.default_message())
        grid.addWidget(self.message_label, 9, 0, 1, -1)
        self.pw_label = QLabel(_('Password'))
        self.pw_label.setVisible(self.password_required)
        self.pw = PasswordLineEdit()
        self.pw.setVisible(self.password_required)
        grid.addWidget(self.pw_label, 11, 0)
        grid.addWidget(self.pw, 11, 1, 1, -1)

        self.send_button = QPushButton(_('Send'))
        self.send_button.clicked.connect(self.on_send)
        self.send_button.setDefault(True)

        self.preview_button = QPushButton(_('Preview'))
        self.preview_button.clicked.connect(self.on_preview)
        self.preview_button.setDefault(True)

        vbox.addLayout(
            Buttons(CancelButton(self), self.preview_button, self.send_button))

        # set default to ASAP checked
        self.asap_check.setChecked(True)
        self.toggle_target()

        self.update()
        self.is_send = False
Beispiel #6
0
class ConfirmTxDialog(WindowModalDialog):
    # set fee and return password (after pw check)

    def __init__(self, *, window: 'ElectrumWindow', inputs, outputs,
                 output_value: Union[int, str], is_sweep: bool):
        WindowModalDialog.__init__(self, window,
                                   _("BitPost Confirm Transaction"))
        self.main_window = window
        self.inputs = inputs
        self.outputs = outputs
        self.output_value = output_value
        self.delay = None
        self.target = None
        self.txs = []
        self.config = window.config
        self.wallet = window.wallet
        self.not_enough_funds = False
        self.no_dynfee_estimates = False
        self.needs_update = False
        self.is_sweep = is_sweep
        self.password_required = self.wallet.has_keystore_encryption(
        ) and not is_sweep
        self.num_txs = int(window.config.get('bitpost_num_txs'))

        self.imax_fees = 0
        self.imax_size = 0

        self.build_gui()

    def build_gui(self):
        vbox = QVBoxLayout()
        self.setLayout(vbox)
        grid = QGridLayout()
        vbox.addLayout(grid)
        self.amount_label = QLabel('')

        grid.addWidget(QLabel(_("Target for confirmation")), 0, 0)
        self.qtarget = QDateTimeEdit(QDateTime.currentDateTime().addSecs(
            int(self.main_window.config.get('bitpost_target_interval')) * 60))
        grid.addWidget(self.qtarget, 0, 1)

        self.asap_check = QCheckBox("ASAP")
        self.asap_check.clicked.connect(self.toggle_target)
        grid.addWidget(self.asap_check, 0, 2)

        grid.addWidget(QLabel(_("Maximum Fee")), 2, 0)
        self.max_fees = QLineEdit(
            str(self.main_window.config.get('bitpost_max_fee')))
        self.max_fees.textChanged.connect(self.change_max_fees)
        grid.addWidget(self.max_fees, 2, 1)
        self.fee_combo = QComboBox()
        fee_combo_values = get_fee_units(
            self.main_window,
            self.main_window.config.get('bitpost_max_fee_unit'))
        self.fee_combo.addItems(fee_combo_values)
        grid.addWidget(self.fee_combo, 2, 2)

        self.schedule_check = QCheckBox(_("Schedule transaction"))
        self.schedule_check.clicked.connect(self.toggle_delay)
        grid.addWidget(self.schedule_check, 3, 0, 1, -1)
        self.qdelay = QDateTimeEdit(QDateTime.currentDateTime())
        grid.addWidget(self.qdelay, 4, 0)
        sp_retain = QSizePolicy(self.qdelay.sizePolicy())
        sp_retain.setRetainSizeWhenHidden(True)
        self.qdelay.setSizePolicy(sp_retain)
        self.qdelay.setVisible(False)

        self.message_label = QLabel(self.default_message())
        grid.addWidget(self.message_label, 9, 0, 1, -1)
        self.pw_label = QLabel(_('Password'))
        self.pw_label.setVisible(self.password_required)
        self.pw = PasswordLineEdit()
        self.pw.setVisible(self.password_required)
        grid.addWidget(self.pw_label, 11, 0)
        grid.addWidget(self.pw, 11, 1, 1, -1)

        self.send_button = QPushButton(_('Send'))
        self.send_button.clicked.connect(self.on_send)
        self.send_button.setDefault(True)

        self.preview_button = QPushButton(_('Preview'))
        self.preview_button.clicked.connect(self.on_preview)
        self.preview_button.setDefault(True)

        vbox.addLayout(
            Buttons(CancelButton(self), self.preview_button, self.send_button))

        # set default to ASAP checked
        self.asap_check.setChecked(True)
        self.toggle_target()

        self.update()
        self.is_send = False

    def toggle_target(self):
        if self.asap_check.isChecked():
            self.qtarget.setEnabled(False)
            self.qdelay.setEnabled(False)
            self.schedule_check.setEnabled(False)
        else:
            self.qtarget.setEnabled(True)
            self.qdelay.setEnabled(True)
            self.schedule_check.setEnabled(True)

    def toggle_delay(self):
        if self.schedule_check.isChecked():
            self.qdelay.setVisible(True)

        else:
            self.qdelay.setVisible(False)

    def change_max_fees(self):
        pass

    def default_message(self):
        return _('Enter your password to proceed'
                 ) if self.password_required else _('Click Send to proceed')

    def on_preview(self):
        password = self.pw.text() or None
        BlockingWaitingDialog(self.main_window, _("Preparing transaction..."),
                              self.prepare_txs)
        if len(self.txs) <= 0:
            return
        d = PreviewTxsDialog(window=self.main_window,
                             txs=self.txs,
                             password=password,
                             is_sweep=self.is_sweep)
        cancelled, is_send, password = d.run()
        if cancelled:
            return
        if is_send:
            self.pw.setText(password)
            self.send()

    def run(self):
        cancelled = not self.exec_()
        password = self.pw.text() or None
        return cancelled, self.is_send, password, self.txs, self.target, self.delay, self.imax_fees, self.imax_size

    def send(self):
        password = self.pw.text() or None
        if self.password_required:
            if password is None:
                self.main_window.show_error(_("Password required"),
                                            parent=self)
                return
            try:
                self.wallet.check_password(password)
            except Exception as e:
                self.main_window.show_error(str(e), parent=self)
                return
        self.is_send = True
        self.target = self.qtarget.dateTime().toPyDateTime().timestamp()
        if self.asap_check.isChecked():
            self.target = round(datetime.now().timestamp() + 20 * 60)

        if self.schedule_check.isChecked():
            self.delay = self.qdelay.dateTime().toPyDateTime()
        else:
            self.delay = self.main_window.config.get('bitpost_delay', 0)

        if self.target < self.delay:
            self.main_window.show_error(
                _("Target should be greater than delay"))
            return
        self.is_send = True
        if self.is_send:
            self.accept()
        else:
            print("ERROR: is_send is false")

    def on_send(self):
        BlockingWaitingDialog(self.main_window, _("Preparing transaction..."),
                              self.prepare_txs)
        if len(self.txs) <= 0:
            return
        self.send()

    def get_feerates(self, estimated_size):
        if self.config.get('testnet'):
            testnet = True
        else:
            testnet = False
        bitpost_interface = BitpostInterface(testnet=testnet)
        max_feerate = self.calculate_max_feerate(estimated_size,
                                                 self.fee_combo.currentText())
        return bitpost_interface.get_feerates(max_feerate, size=self.num_txs)

    def calculate_max_feerate(self, estimated_size, fee_unit):
        raw_max_fee = float(self.max_fees.text())
        if fee_unit == 'sats/byte':
            return raw_max_fee
        elif fee_unit == 'sats':
            return raw_max_fee / estimated_size
        else:
            max_sats = 100_000_000 * raw_max_fee / float(
                self.main_window.fx.exchange_rate())
            return max_sats / estimated_size

    def prepare_txs(self):
        try:
            print('prepare txs')
            self.prepare_txs_by_bumping_fee()
        except CannotBumpFee as ex:
            print("cannot bump fee", ex)
            self.prepare_txs_manually()
        except Exception as e:
            print(e)

    def prepare_txs_manually(self):
        print('prepare tx manually')
        max_fee = int(
            200 *
            self.calculate_max_feerate(200, self.fee_combo.currentText()))
        highest_fee_tx = self.make_tx(max_fee)

        self.imax_size = est_size = highest_fee_tx.estimated_size()
        self.imax_fees = max_fee = int(
            est_size *
            self.calculate_max_feerate(est_size, self.fee_combo.currentText()))

        can_be_change = lambda o: self.main_window.wallet.is_change(
            o.address) and self.main_window.wallet.is_mine(o.address)
        change_index = max([
            i for i in range(len(highest_fee_tx.outputs()))
            if can_be_change(highest_fee_tx.outputs()[i])
        ])
        max_feerate = (
            highest_fee_tx.input_value() -
            highest_fee_tx.output_value()) / highest_fee_tx.estimated_size()
        feerates = self.get_feerates(max_feerate)

        highest_fee_tx.set_rbf(True)
        self.txs = []
        for fee in feerates:
            tx = self.main_window.wallet.make_unsigned_transaction(
                coins=highest_fee_tx.inputs(),
                outputs=highest_fee_tx.outputs())
            new_change = highest_fee_tx.outputs()[change_index].value + int(
                abs(max_feerate - fee) * highest_fee_tx.estimated_size())
            tx.outputs()[change_index].value = new_change
            self.txs.append(tx)

        self.not_enough_funds = False
        self.no_dynfee_estimates = False

    def prepare_txs_by_bumping_fee(self):
        try:
            base_tx = self.make_tx(0)
            est_size = base_tx.estimated_size()
            feerates = self.get_feerates(est_size)
            base_tx.set_rbf(True)
            base_tx.serialize_to_network()
            for fee in feerates:
                tx = self.bump_fee(base_tx, fee)
                tx.set_rbf(True)
                self.txs.append(tx)
                self.imax_fees = max(self.imax_fees, tx.get_fee())
                self.imax_size = max(self.imax_size, tx.estimated_size())
            self.not_enough_funds = False
            self.no_dynfee_estimates = False
        except NotEnoughFunds:
            self.not_enough_funds = True
            self.txs = []
            print("not enought funds")
            return
        except NoDynamicFeeEstimates:
            self.no_dynfee_estimates = True
            self.txs = []
            print("no dynamic fee estimation")
            try:
                self.txs = [self.make_tx(0)]
            except BaseException:
                return
        except InternalAddressCorruption as e:
            self.txs = []
            self.main_window.show_error(str(e))
            print("Internal address corruption")
            return
        except BitpostDownException:
            self.main_window.show_error(_("Fee Rates Service Not Available"),
                                        parent=self)
            self.is_send = False
            print("bitpost down")
            return
        except Exception as e:
            self.txs = []
            print("Exception", e)
            self.main_window.show_error(_("Exception: " + str(e)),
                                        parent=self.main_window)
            return

    def bump_fee(self, tx, new_fee):

        inputs = tx.inputs()
        coco = []
        for c in self.inputs:

            if c not in inputs:

                coco.append(c)
        try:

            self.main_window.logger.debug(str(new_fee) + "bump fee method 1")
            tx_out = self.main_window.wallet._bump_fee_through_coinchooser(
                tx=tx, new_fee_rate=new_fee, coins=coco)

        except Exception as ex:
            if all(
                    self.main_window.wallet.is_mine(o.address)
                    for o in list(tx.outputs())):
                raise ex
            self.window.show_error(
                _("Not enought funds, please add more inputs or reduce max fee"
                  ))
            raise NotEnoughFunds
        return tx_out

    def make_tx(self, fee_est):
        tx = self.main_window.wallet.make_unsigned_transaction(
            coins=self.inputs,
            outputs=self.outputs,
            fee=fee_est,
            is_sweep=self.is_sweep)

        return tx
Beispiel #7
0
class LedgerAuthDialog(QDialog):
    def __init__(self, handler, data):
        '''Ask user for 2nd factor authentication. Support text and security card methods.
        Use last method from settings, but support downgrade.
        '''
        QDialog.__init__(self, handler.top_level_window())
        self.handler = handler
        self.txdata = data
        self.idxs = self.txdata['keycardData'] if self.txdata['confirmationType'] > 1 else ''
        self.setMinimumWidth(650)
        self.setWindowTitle(_("Ledger Wallet Authentication"))
        self.cfg = copy.deepcopy(self.handler.win.wallet.get_keystore().cfg)
        self.dongle = self.handler.win.wallet.get_keystore().get_client().dongle
        self.pin = ''
        
        self.devmode = self.getDevice2FAMode()
        if self.devmode == 0x11 or self.txdata['confirmationType'] == 1:
            self.cfg['mode'] = 0
        
        vbox = QVBoxLayout()
        self.setLayout(vbox)
        
        def on_change_mode(idx):
            self.cfg['mode'] = 0 if self.devmode == 0x11 else idx if idx > 0 else 1
            if self.cfg['mode'] > 0:
                self.handler.win.wallet.get_keystore().cfg = self.cfg
                self.handler.win.wallet.save_keystore()
            self.update_dlg()
        def return_pin():
            self.pin = self.pintxt.text() if self.txdata['confirmationType'] == 1 else self.cardtxt.text() 
            if self.cfg['mode'] == 1:
                self.pin = ''.join(chr(int(str(i),16)) for i in self.pin)
            self.accept()
        
        self.modebox = QWidget()
        modelayout = QHBoxLayout()
        self.modebox.setLayout(modelayout)
        modelayout.addWidget(QLabel(_("Method:")))
        self.modes = QComboBox()
        modelayout.addWidget(self.modes, 2)
        modelayout.addStretch(1)
        self.modebox.setMaximumHeight(50)
        vbox.addWidget(self.modebox)
        
        self.populate_modes()
        self.modes.currentIndexChanged.connect(on_change_mode)

        self.helpmsg = QTextEdit()
        self.helpmsg.setStyleSheet("QTextEdit { color:black; background-color: lightgray; }")
        self.helpmsg.setReadOnly(True)
        vbox.addWidget(self.helpmsg)
        
        self.pinbox = QWidget()
        pinlayout = QHBoxLayout()
        self.pinbox.setLayout(pinlayout)
        self.pintxt = PasswordLineEdit()
        self.pintxt.setMaxLength(4)
        self.pintxt.returnPressed.connect(return_pin)
        pinlayout.addWidget(QLabel(_("Enter PIN:")))
        pinlayout.addWidget(self.pintxt)
        pinlayout.addWidget(QLabel(_("NOT DEVICE PIN - see above")))
        pinlayout.addStretch(1)
        self.pinbox.setVisible(self.cfg['mode'] == 0)
        vbox.addWidget(self.pinbox)
                    
        self.cardbox = QWidget()
        card = QVBoxLayout()
        self.cardbox.setLayout(card)
        self.addrtext = QTextEdit()
        self.addrtext.setStyleSheet('''
            QTextEdit {
                color:blue; background-color:lightgray; padding:15px 10px; border:none;
                font-size:20pt; font-family: "Courier New", monospace; }
        ''')
        self.addrtext.setReadOnly(True)
        self.addrtext.setMaximumHeight(130)
        card.addWidget(self.addrtext)
        
        def pin_changed(s):
            if len(s) < len(self.idxs):
                i = self.idxs[len(s)]
                addr = self.txdata['address']
                if not constants.net.TESTNET:
                    text = addr[:i] + '<u><b>' + addr[i:i+1] + '</u></b>' + addr[i+1:]
                else:
                    # pin needs to be created from mainnet address
                    addr_mainnet = bitcoin.script_to_address(bitcoin.address_to_script(addr), net=constants.BitcoinMainnet)
                    addr_mainnet = addr_mainnet[:i] + '<u><b>' + addr_mainnet[i:i+1] + '</u></b>' + addr_mainnet[i+1:]
                    text = str(addr) + '\n' + str(addr_mainnet)
                self.addrtext.setHtml(str(text))
            else:
                self.addrtext.setHtml(_("Press Enter"))
                
        pin_changed('')    
        cardpin = QHBoxLayout()
        cardpin.addWidget(QLabel(_("Enter PIN:")))
        self.cardtxt = PasswordLineEdit()
        self.cardtxt.setMaxLength(len(self.idxs))
        self.cardtxt.textChanged.connect(pin_changed)
        self.cardtxt.returnPressed.connect(return_pin)
        cardpin.addWidget(self.cardtxt)
        cardpin.addWidget(QLabel(_("NOT DEVICE PIN - see above")))
        cardpin.addStretch(1)
        card.addLayout(cardpin)
        self.cardbox.setVisible(self.cfg['mode'] == 1)
        vbox.addWidget(self.cardbox)

        self.update_dlg()

    def populate_modes(self):
        self.modes.blockSignals(True)
        self.modes.clear()
        self.modes.addItem(_("Summary Text PIN (requires dongle replugging)") if self.txdata['confirmationType'] == 1 else _("Summary Text PIN is Disabled"))
        if self.txdata['confirmationType'] > 1:
            self.modes.addItem(_("Security Card Challenge"))
        self.modes.blockSignals(False)
        
    def update_dlg(self):
        self.modes.setCurrentIndex(self.cfg['mode'])
        self.modebox.setVisible(True)
        self.helpmsg.setText(helpTxt[self.cfg['mode']])
        self.helpmsg.setMinimumHeight(180 if self.txdata['confirmationType'] == 1 else 100)
        self.helpmsg.setVisible(True)
        self.pinbox.setVisible(self.cfg['mode'] == 0)
        self.cardbox.setVisible(self.cfg['mode'] == 1)
        self.pintxt.setFocus(True) if self.cfg['mode'] == 0 else self.cardtxt.setFocus(True)
        self.setMaximumHeight(400)

    def getDevice2FAMode(self):
        apdu = [0xe0, 0x24, 0x01, 0x00, 0x00, 0x01] # get 2fa mode
        try:
            mode = self.dongle.exchange( bytearray(apdu) )
            return mode
        except BTChipException as e:
            _logger.debug('Device getMode Failed')
        return 0x11