Esempio n. 1
0
class Create(QDialog, MessageBoxMixin):
    def __init__(self, parent, plugin, wallet_name, password, manager):
        QDialog.__init__(self, parent)
        self.main_window = parent
        self.wallet = parent.wallet
        self.plugin = plugin
        self.wallet_name = wallet_name
        self.config = parent.config
        self.password = None
        self.contract = None
        self.version = 1
        if self.wallet.has_password():
            self.main_window.show_error(
                _("Plugin requires password. It will get access to your private keys."
                  ))
            self.password = parent.password_dialog()
            if not self.password:
                print("no password")
                self.plugin.switch_to(Intro, self.wallet_name, None, None)
        self.fund_domain = None
        self.fund_change_address = None
        self.sender_address = self.wallet.get_unused_address()
        self.receiver_address = None
        self.arbiter_address = None
        self.total_value = 0
        index = self.wallet.get_address_index(self.sender_address)
        key = self.wallet.keystore.get_private_key(index, self.password)
        self.privkey = int.from_bytes(key[0], 'big')

        if isinstance(self.wallet, Multisig_Wallet):
            self.main_window.show_error(
                "Simple Escrow Plugin is designed for single signature wallets."
            )

        vbox = QVBoxLayout()
        self.setLayout(vbox)
        hbox = QHBoxLayout()
        vbox.addLayout(hbox)
        l = QLabel("<b>%s</b>" % (_("Creatin 2-of-3 Multisig contract:")))
        hbox.addWidget(l)
        hbox.addStretch(1)
        b = QPushButton(_("Home"))
        b.clicked.connect(
            lambda: self.plugin.switch_to(Intro, self.wallet_name, None, None))
        hbox.addWidget(b)
        l = QLabel(
            _("Refund address") +
            ": auto (this wallet)")  # self.refreshing_address.to_ui_string())
        vbox.addWidget(l)
        l = QLabel(_("Receiver address: "))
        vbox.addWidget(l)

        self.receiver_address_wid = QLineEdit()
        self.receiver_address_wid.textEdited.connect(
            self.new_contract_info_changed)
        vbox.addWidget(self.receiver_address_wid)

        l = QLabel(_("Arbiter address: "))
        vbox.addWidget(l)

        self.arbiter_address_wid = QLineEdit()
        self.arbiter_address_wid.textEdited.connect(
            self.new_contract_info_changed)
        vbox.addWidget(self.arbiter_address_wid)

        self.value_wid = BTCAmountEdit(self.main_window.get_decimal_point)
        self.value_wid.setAmount(1000000)
        self.value_wid.textEdited.connect(self.new_contract_info_changed)
        vbox.addWidget(self.value_wid)
        b = QPushButton(_("Create Escrow Contract"))
        b.clicked.connect(self.create_new_contract)
        vbox.addStretch(1)
        vbox.addWidget(b)
        self.create_button = b
        self.create_button.setDisabled(True)
        vbox.addStretch(1)

    def new_contract_info_changed(self, ):
        # if any of the txid/out#/value changes
        try:
            self.total_value = self.value_wid.get_amount()
            self.receiver_address = Address.from_string(
                self.receiver_address_wid.text())
            self.arbiter_address = Address.from_string(
                self.arbiter_address_wid.text())
            addresses = [
                self.receiver_address, self.sender_address,
                self.arbiter_address
            ]
        except:
            self.create_button.setDisabled(True)
        else:
            self.create_button.setDisabled(False)
            self.contract = MultisigContract(addresses,
                                             v=self.version,
                                             data=None)

    def build_otputs(self):
        outputs = []
        outputs.append((TYPE_SCRIPT, ScriptOutput(self.contract.op_return), 0))
        for a in self.contract.addresses:
            outputs.append((TYPE_ADDRESS, a, 546))
        outputs.append((TYPE_ADDRESS, self.contract.address, self.total_value))
        return outputs

    def create_new_contract(self, ):
        yorn = self.main_window.question(
            _("Do you wish to create the Sender Contract?"))
        if not yorn:
            return
        outputs = self.build_otputs()
        receiver_is_mine = self.wallet.is_mine(self.contract.addresses[0])
        escrow_is_mine = self.wallet.is_mine(self.contract.addresses[2])
        if receiver_is_mine and escrow_is_mine:
            self.show_error(
                "All three participants are in your wallet. Such contract will be impossible to terminate. Aborting."
            )
            return
        try:
            tx = self.wallet.mktx(outputs,
                                  self.password,
                                  self.config,
                                  domain=self.fund_domain,
                                  change_addr=self.fund_change_address)
        except NotEnoughFunds:
            return self.show_critical(
                _("Not enough balance to fund smart contract."))
        except Exception as e:
            return self.show_critical(repr(e))
        tx.version = 2
        try:
            self.main_window.network.broadcast_transaction2(tx)
        except:
            pass
        self.create_button.setText("Creating Escrow Contract...")
        self.create_button.setDisabled(True)
        self.plugin.switch_to(Intro, self.wallet_name, None, None)
Esempio n. 2
0
class PaymentDialog(QDialog, MessageBoxMixin):
    def __init__(self, window, plugin, payment_data):
        # We want to be a top-level window
        QDialog.__init__(self, parent=None)

        #print("PaymentDialog", "payment_data =", payment_data)
        self.payment_data = payment_data

        self.plugin = plugin

        # WARNING: Copying some attributes so PayToEdit() will work.
        self.main_window = window
        self.contacts = self.main_window.contacts
        self.is_max = self.main_window.is_max  # Unused, as we do not use max.
        self.completions = self.main_window.completions

        self.count_labels = [
            "Disabled",
            "Once",
            "Always",
        ]
        self.display_count_labels = [
            "Always",
        ]
        run_always_index = self.count_labels.index("Always")

        # NOTE: User entered data, for verification purposes (enabling save/create), and subsequent dispatch on button press.

        self.value_description = ""
        self.value_amount = None
        self.value_payto_outputs = []
        self.value_run_occurrences = self.count_labels.index("Always")
        self.set_flags(0 if self.payment_data is None else self.
                       payment_data[PAYMENT_FLAGS])

        if self.payment_data is not None:
            self.value_description = self.payment_data[PAYMENT_DESCRIPTION]
            self.value_amount = self.payment_data[PAYMENT_AMOUNT]
            self.value_run_occurrences = self.payment_data[PAYMENT_COUNT0]

        # NOTE: Set up the UI for this dialog.
        self.setMinimumWidth(350)
        if payment_data is None:
            self.setWindowTitle(_("Create New Scheduled Payment"))
        else:
            self.setWindowTitle(_("Edit Existing Scheduled Payment"))

        formLayout = QFormLayout()
        self.setLayout(formLayout)

        # Input fields.
        msg = _('Description of the payment (not mandatory).') + '\n\n' + _(
            'The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.'
        )
        self.description_label = HelpLabel(_('Description'), msg)
        self.description_edit = MyLineEdit()
        self.description_edit.setText(self.value_description)
        formLayout.addRow(self.description_label, self.description_edit)

        msg = _('How much to pay.') + '\n\n' + _('Unhelpful descriptive text')
        self.amount_label = HelpLabel(_('Amount'), msg)
        self.amount_e = BTCAmountEdit(
            window.get_decimal_point
        )  # WARNING: This has to be named this, as PayToEdit accesses it.
        self.amount_e.setAmount(self.value_amount)
        # WARNING: This needs to be present before PayToEdit is constructed (as that accesses it's attribute on this object),
        # but added to the layout after in order to try and reduce the "cleared amount" problem that happens when an address
        # is entered (perhaps on a selected completion, i.e. of a contact).

        # WARNING: This will clear the amount when an address is set, see PayToEdit.check_text.
        self.payto_edit = PayToEdit(self)
        msg = _('Recipient of the funds.') + '\n\n' + _(
            'You may enter a Bitcoin Cash address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin Cash address)'
        )
        payto_label = HelpLabel(_('Pay to'), msg)
        formLayout.addRow(payto_label, self.payto_edit)

        def set_payment_address(address):
            self.payto_edit.payto_address = bitcoin.TYPE_ADDRESS, Address.from_string(
                address)
            self.value_payto_outputs = self.payto_edit.get_outputs(False)
            contact_name = None
            if address in window.wallet.contacts.keys():
                contact_type, contact_name = window.wallet.contacts[address]
            if contact_name is not None:
                self.payto_edit.setText(contact_name + ' <' + address + '>')
            else:
                self.payto_edit.setText(address)

        if payment_data is not None:
            set_payment_address(payment_data[PAYMENT_ADDRESS])

        completer = QCompleter()
        completer.setCaseSensitivity(False)
        self.payto_edit.setCompleter(completer)
        completer.setModel(self.completions)

        # WARNING: We created this before PayToEdit and add it to the layout after, due to the dependency issues with PayToEdit accessing `self.amount_e`.
        formLayout.addRow(self.amount_label, self.amount_e)

        if payment_data is not None:
            text = _("No payments made.")
            if payment_data[PAYMENT_DATELASTPAID] is not None:
                text = datetime.datetime.fromtimestamp(
                    payment_data[PAYMENT_DATELASTPAID]).strftime("%c")
            textLabel = QLabel(text)
            label = HelpLabel(
                _('Last Paid'),
                _('Date last paid.') + '\n\n' +
                _('The date at which this scheduled payment was last meant to send a transaction to the network, which the user acted on'
                  ))
            formLayout.addRow(label, textLabel)

        count_combo = QComboBox()
        count_combo.addItems(self.display_count_labels)
        count_combo.setCurrentIndex(
            self.display_count_labels.index(
                self.count_labels[self.value_run_occurrences]))
        msg = _('Repeat') + '\n\n' + _(
            'The number of times the payment should be made.')
        label = HelpLabel(_('Repeat'), msg)
        formLayout.addRow(label, count_combo)

        # The setting will be cleared if the wallet somehow becomes unencrypted, and will only be available for unencrypted wallets.
        isEnabled = not self.main_window.wallet.has_password(
        ) and window.config.fee_per_kb() is not None
        self.value_autopayment = self.value_autopayment and isEnabled
        # Will show it for now, for encrypted wallets.  Might be less confusing not to show it.
        self.autoPaymentCheckbox = QCheckBox(
            _("Make this payment automatically."))
        self.autoPaymentCheckbox.setToolTip(
            _("Requirements") + ":\n" +
            _("1. The wallet must not have a password.") + "\n" +
            _("2. There must be a default fee/kb configured for the wallet." +
              "\n" +
              _("If this checkbox is interactive and not disabled, these requirements are met."
                )))
        self.autoPaymentCheckbox.setChecked(self.value_autopayment)
        self.autoPaymentCheckbox.setEnabled(isEnabled)
        formLayout.addRow(_("Options"), self.autoPaymentCheckbox)

        import importlib
        from . import when_widget
        importlib.reload(when_widget)
        self.whenWidget = when_widget.WhenWidget(_("When"))
        self.whenWidget.setWhen(
            None if payment_data is None else payment_data[PAYMENT_WHEN])
        formLayout.addRow(self.whenWidget)

        # NOTE: Hook up value events and provide handlers.

        def validate_input_values():
            allow_commit = True
            allow_commit = allow_commit and len(self.value_description) > 0
            allow_commit = allow_commit and self.value_amount is not None and self.value_amount > 0
            allow_commit = allow_commit and len(self.value_payto_outputs) > 0
            allow_commit = allow_commit and self.value_run_occurrences == run_always_index
            # allow_commit = allow_commit and self.value_run_occurrences > -1 and self.value_run_occurrences < len(count_labels)
            self.save_button.setEnabled(allow_commit)

        def on_run_occurrences_changed(unknown):
            self.value_run_occurrences = self.count_labels.index(
                self.display_count_labels[count_combo.currentIndex()])
            validate_input_values()

        count_combo.currentIndexChanged.connect(on_run_occurrences_changed)

        def on_recipient_changed():
            self.value_payto_outputs = self.payto_edit.get_outputs(False)
            validate_input_values()

        self.payto_edit.textChanged.connect(on_recipient_changed)

        def on_amount_changed():
            self.value_amount = self.amount_e.get_amount()
            validate_input_values()

        self.amount_e.textChanged.connect(on_amount_changed)

        def on_description_changed():
            self.value_description = self.description_edit.text().strip()
            validate_input_values()

        self.description_edit.textChanged.connect(on_description_changed)

        def on_autopayment_toggled(v):
            self.value_autopayment = v == Qt.Checked

        self.autoPaymentCheckbox.stateChanged.connect(on_autopayment_toggled)

        # Buttons at bottom right.
        save_button_text = _("Save")
        if payment_data is None:
            save_button_text = _("Create")
        self.save_button = b = QPushButton(save_button_text)
        b.clicked.connect(self.save)

        self.cancel_button = b = QPushButton(_("Cancel"))
        b.clicked.connect(self.close)
        b.setDefault(True)

        self.buttons = [self.save_button, self.cancel_button]

        hbox = QHBoxLayout()
        #hbox.addLayout(Buttons(*self.sharing_buttons))
        hbox.addStretch(1)
        hbox.addLayout(Buttons(*self.buttons))
        formLayout.addRow(hbox)

        validate_input_values()
        self.update()

    def save(self):
        # NOTE: This is in lieu of running some kind of updater that updates the esetimated time every second.
        if self.whenWidget.updateEstimatedTime():
            if not self.question(_(
                    "The next matching date passed between the last time you modified the date, and when you clicked on save.  Do you wish to proceed anyway?"
            ),
                                 title=_("Next Matching Date Changed")):
                return

        data_id = None
        if self.payment_data is not None:
            data_id = self.payment_data[PAYMENT_ID]

        payment_data = [None] * PAYMENT_ENTRY_LENGTH
        payment_data[PAYMENT_ID] = data_id
        payment_data[PAYMENT_ADDRESS] = self.value_payto_outputs[0][
            1].to_storage_string()
        payment_data[PAYMENT_AMOUNT] = self.value_amount
        payment_data[PAYMENT_DESCRIPTION] = self.value_description
        payment_data[PAYMENT_COUNT0] = self.value_run_occurrences
        payment_data[PAYMENT_WHEN] = self.whenWidget.getWhen().toText()
        payment_data[PAYMENT_DATENEXTPAID] = self.whenWidget.getEstimatedTime()
        payment_data[PAYMENT_FLAGS] = self.get_flags()

        wallet_name = self.main_window.wallet.basename()
        self.plugin.update_payment(wallet_name, payment_data)

        self.close()

    def closeEvent(self, event):
        wallet_name = self.main_window.wallet.basename()
        if self.payment_data is None:
            payment_id = None
        else:
            payment_id = self.payment_data[PAYMENT_ID]
        self.plugin.on_payment_editor_closed(wallet_name, payment_id)
        event.accept()

    def onTimeChanged(self, clock_current_time):
        self.whenWidget.updateEstimatedTime(currentTime=clock_current_time)

    def get_flags(self):
        flags = 0
        if self.value_autopayment:
            flags |= PAYMENT_FLAG_AUTOPAY
        return flags

    def set_flags(self, flags):
        self.value_autopayment = flags & PAYMENT_FLAG_AUTOPAY == PAYMENT_FLAG_AUTOPAY

    def lock_amount(self, flag):  # WARNING: Copied as needed for PayToEdit
        self.amount_e.setFrozen(flag)

    def do_update_fee(self):  # WARNING: Copied as needed for PayToEdit
        pass

    def pay_to_URI(self, URI):  # WARNING: Copied as needed for PayToEdit
        if not URI:
            return
        try:
            out = web.parse_URI(URI, self.on_pr)
        except Exception as e:
            self.show_error(_('Invalid bitcoincash URI:') + '\n' + str(e))
            return
        r = out.get('r')
        sig = out.get('sig')
        name = out.get('name')
        if r or (name and sig):
            self.prepare_for_payment_request()
            return
        address = out.get('address')
        amount = out.get('amount')
        label = out.get('label')
        message = out.get('message')
        # use label as description (not BIP21 compliant)
        if label and not message:
            message = label
        if address:
            self.payto_edit.setText(address)
        if message:
            self.description_edit.setText(message)
        if amount:
            self.amount_e.setAmount(amount)
            self.amount_e.textEdited.emit("")

    def prepare_for_payment_request(
            self):  # WARNING: Copied as needed for PayToEdit
        self.payto_edit.is_pr = True
        for e in [self.payto_edit, self.amount_e, self.description_edit]:
            e.setFrozen(True)
        self.payto_edit.setText(_("please wait..."))
        return True

    on_pr = None
Esempio n. 3
0
class Create(QDialog, MessageBoxMixin):
    def __init__(self, parent, plugin, wallet_name, password, manager):
        QDialog.__init__(self, parent)
        self.main_window = parent
        self.wallet = parent.wallet
        self.plugin = plugin
        self.wallet_name = wallet_name
        self.config = parent.config
        self.password = None
        self.contract = None
        self.version = 1
        if self.wallet.has_password():
            self.main_window.show_error(
                _("Plugin requires password. It will get access to your private keys."
                  ))
            self.password = parent.password_dialog()
            if not self.password:
                print("no password")
                self.plugin.switch_to(Intro, self.wallet_name, None, None)
        self.fund_domain = None
        self.fund_change_address = None
        self.mecenas_address = self.wallet.get_unused_address()
        self.protege_address = None
        self.escrow_address = None
        self.addresses = []
        self.total_value = 0
        self.rpayment_value = 0
        self.rpayment_time = 0
        self.reps = 0
        index = self.wallet.get_address_index(self.mecenas_address)
        key = self.wallet.keystore.get_private_key(index, self.password)
        self.privkey = int.from_bytes(key[0], 'big')

        if isinstance(self.wallet, Multisig_Wallet):
            self.main_window.show_error(
                "Mecenas is designed for single signature wallet only right now"
            )

        vbox = QVBoxLayout()
        self.setLayout(vbox)
        hbox = QHBoxLayout()
        vbox.addLayout(hbox)
        l = QLabel("<b>%s</b>" % (_("Creatin Mecenas contract:")))
        hbox.addWidget(l)
        hbox.addStretch(1)
        b = QPushButton(_("Home"))
        b.clicked.connect(
            lambda: self.plugin.switch_to(Intro, self.wallet_name, None, None))
        hbox.addWidget(b)
        l = QLabel(
            _("Redeem address") +
            ": auto (this wallet)")  # self.refreshing_address.to_ui_string())
        vbox.addWidget(l)

        l = QLabel(_("Protege address: "))
        vbox.addWidget(l)

        self.protege_address_wid = QLineEdit()
        self.protege_address_wid.textEdited.connect(self.mecenate_info_changed)
        vbox.addWidget(self.protege_address_wid)

        grid = QGridLayout()
        vbox.addLayout(grid)

        l = QLabel(_("Recurring payment value: "))
        grid.addWidget(l, 0, 0)
        l = QLabel(_("Repetitions:"))
        grid.addWidget(l, 0, 1)

        l = QLabel(_("Period (days): "))
        grid.addWidget(l, 0, 2)

        self.rpayment_value_wid = BTCAmountEdit(
            self.main_window.get_decimal_point)
        self.rpayment_value_wid.setAmount(1000000)
        self.rpayment_value_wid.textEdited.connect(self.mecenate_info_changed)

        self.repetitions = QLineEdit()
        self.repetitions.textEdited.connect(self.mecenate_info_changed)
        grid.addWidget(self.repetitions, 1, 1)

        self.rpayment_time_wid = QLineEdit()
        self.rpayment_time_wid.setText("30")
        self.rpayment_time_wid.textEdited.connect(self.mecenate_info_changed)
        grid.addWidget(self.rpayment_value_wid, 1, 0)
        grid.addWidget(self.rpayment_time_wid, 1, 2)
        grid.addWidget(QLabel("Total contract value:"), 2, 0)
        self.total_label = QLabel("0")
        hbox = QHBoxLayout()
        hbox.addWidget(self.total_label)
        hbox.addStretch(1)
        hbox.addWidget(QLabel("Total time:"))
        #grid.addWidget(self.total_label,2,1)
        grid.addLayout(hbox, 2, 1)
        self.total_time_label = QLabel("0")
        grid.addWidget(self.total_time_label, 2, 2)
        self.advanced_wid = AdvancedWid(self)
        self.advanced_wid.toggle_sig.connect(self.mecenate_info_changed)
        vbox.addWidget(self.advanced_wid)
        b = QPushButton(_("Create Mecenas Contract"))
        b.clicked.connect(self.create_mecenat)
        vbox.addStretch(1)
        vbox.addWidget(b)
        self.create_button = b
        self.create_button.setDisabled(True)
        vbox.addStretch(1)

    def mecenate_info_changed(self, ):
        # if any of the txid/out#/value changes
        try:
            self.protege_address = Address.from_string(
                self.protege_address_wid.text())
            self.reps = int(self.repetitions.text())
            self.rpayment_time = int(
                self.rpayment_time_wid.text()) * 3600 * 24 // 512
            self.rpayment_value = self.rpayment_value_wid.get_amount()
            total_time = int(self.rpayment_time_wid.text()) * self.reps
            self.total_time_label.setText("<b>%s</b>" %
                                          (str(total_time) + " days"))

            if self.advanced_wid.option == 2:
                self.version = 2
                self.addresses = [self.protege_address, self.mecenas_address]
            elif self.advanced_wid.option == 3:
                self.version = 3
                self.escrow_address = Address.from_string(
                    self.advanced_wid.escrow_address.text())
                self.addresses = [
                    self.protege_address, self.mecenas_address,
                    self.escrow_address
                ]
            elif self.advanced_wid.option == 1:
                self.version = 1.1
                self.addresses = [self.protege_address, self.mecenas_address]
            elif self.advanced_wid.option == 4:
                self.version = 1
                self.addresses = [self.protege_address, self.mecenas_address]
            self.total_value = self.reps * (self.rpayment_value +
                                            MecenasContract.fee(self.version))
            self.total_label.setText(
                "<b>%s</b>" %
                (self.main_window.format_amount(self.total_value) + " " +
                 self.main_window.base_unit()))

        except Exception as e:
            self.create_button.setDisabled(True)
            print(e)
        else:
            self.create_button.setDisabled(False)
            # PROTEGE is 0, MECENAS is 1
            self.contract = MecenasContract(
                self.addresses,
                v=self.version,
                data=[self.rpayment_time, self.rpayment_value])

    def build_otputs(self):
        outputs = []
        # convention used everywhere else in this plugin is is 0 for protege and 1 for mecenas
        # but I did it upside down here by mistake
        outputs.append((TYPE_SCRIPT, ScriptOutput(self.contract.op_return), 0))
        for a in self.addresses:
            outputs.append((TYPE_ADDRESS, a, 546))
        outputs.append((TYPE_ADDRESS, self.contract.address, self.total_value))
        return outputs

    def create_mecenat(self, ):
        if self.total_value >= 2100000000:
            self.show_error(
                "Contract total value shouldn't be larger than 21 BCH")
            return
        if self.contract.version == 3:
            receiver_is_mine = self.wallet.is_mine(self.contract.addresses[0])
            escrow_is_mine = self.wallet.is_mine(self.contract.addresses[2])
            if receiver_is_mine and escrow_is_mine:
                self.show_error(
                    "All three participants are in your wallet. Such contract will be impossible to terminate. Aborting."
                )
                return
        yorn = self.main_window.question(
            _("Do you wish to create the Mecenas Contract?"))
        if not yorn:
            return
        outputs = self.build_otputs()
        try:
            tx = self.wallet.mktx(outputs,
                                  self.password,
                                  self.config,
                                  domain=self.fund_domain,
                                  change_addr=self.fund_change_address)
        except NotEnoughFunds:
            return self.show_critical(
                _("Not enough balance to fund smart contract."))
        except Exception as e:
            return self.show_critical(repr(e))
        tx.version = 2
        try:
            self.main_window.network.broadcast_transaction2(tx)
            #show_transaction(tx, self.main_window, "Create Contract", prompt_if_unsaved=True)
        except:
            pass
        self.create_button.setText("Creating Mecenas Contract...")
        self.create_button.setDisabled(True)
        self.plugin.switch_to(Intro, self.wallet_name, None, None)