Ejemplo n.º 1
0
class Create(QDialog, MessageBoxMixin):
    def __init__(self, parent, plugin, wallet_name, password, manager):
        QDialog.__init__(self, parent)
        print("Creating")
        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
        if self.wallet.has_password():
            self.main_window.show_error(
                _("Last Will 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.refresh_address = self.wallet.get_unused_address()
        self.inheritor_address = None
        self.cold_address = None
        self.value = 0
        index = self.wallet.get_address_index(self.refresh_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(
                "Last Will 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 Last Will 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(
            _("Refreshing address") +
            ": auto (this wallet)")  # self.refreshing_address.to_ui_string())
        vbox.addWidget(l)

        grid = QGridLayout()
        vbox.addLayout(grid)

        l = QLabel(_("Inheritor address: "))
        grid.addWidget(l, 0, 0)

        l = QLabel(_("Value"))
        grid.addWidget(l, 0, 1)

        self.inheritor_address_wid = QLineEdit()
        self.inheritor_address_wid.textEdited.connect(
            self.inheritance_info_changed)
        grid.addWidget(self.inheritor_address_wid, 1, 0)

        self.inheritance_value_wid = BTCAmountEdit(
            self.main_window.get_decimal_point)
        self.inheritance_value_wid.textEdited.connect(
            self.inheritance_info_changed)
        grid.addWidget(self.inheritance_value_wid, 1, 1)
        l = QLabel(_("My cold wallet address: "))
        grid.addWidget(l, 2, 0)
        self.cold_address_wid = QLineEdit()
        self.cold_address_wid.textEdited.connect(self.inheritance_info_changed)
        grid.addWidget(self.cold_address_wid, 3, 0)
        b = QPushButton(_("Create Last Will"))
        b.clicked.connect(lambda: self.create_last_will())
        self.notification = NotificationWidget(self)
        vbox.addWidget(self.notification)
        vbox.addStretch(1)
        vbox.addWidget(b)
        self.create_button = b
        self.create_button.setDisabled(True)
        vbox.addStretch(1)

    def inheritance_info_changed(self, ):
        # if any of the txid/out#/value changes
        try:
            self.inheritor_address = Address.from_string(
                self.inheritor_address_wid.text())
            self.cold_address = Address.from_string(
                self.cold_address_wid.text())
            self.value = self.inheritance_value_wid.get_amount()
        except:
            self.create_button.setDisabled(True)
        else:
            self.create_button.setDisabled(False)
            addresses = [
                self.refresh_address, self.cold_address, self.inheritor_address
            ]
            self.contract = LastWillContract(addresses)

    def create_last_will(self, ):

        outputs = [
            (TYPE_SCRIPT,
             ScriptOutput(
                 make_opreturn(
                     self.contract.address.to_ui_string().encode('utf8'))), 0),
            (TYPE_ADDRESS, self.refresh_address, self.value + 190),
            (TYPE_ADDRESS, self.cold_address, 546),
            (TYPE_ADDRESS, self.inheritor_address, 546)
        ]
        try:
            tx = self.wallet.mktx(outputs,
                                  self.password,
                                  self.config,
                                  domain=self.fund_domain,
                                  change_addr=self.fund_change_address)
            id = tx.txid()
        except NotEnoughFunds:
            return self.show_critical(
                _("Not enough balance to fund smart contract."))
        except Exception as e:
            return self.show_critical(repr(e))

        # preparing transaction, contract can't give a change
        self.main_window.network.broadcast_transaction2(tx)
        self.create_button.setText("Creating Last Will...")
        self.create_button.setDisabled(True)
        coin = self.wait_for_coin(id, 10)
        self.wallet.add_input_info(coin)
        inputs = [coin]
        outputs = [(TYPE_ADDRESS, self.contract.address, self.value)]
        tx = Transaction.from_io(inputs, outputs, locktime=0)
        tx.version = 2
        show_transaction(tx,
                         self.main_window,
                         "Make Last Will contract",
                         prompt_if_unsaved=True)

        if self.notification.do_anything():
            outputs = self.notification.notification_outputs(
                self.contract.address)
            tx = self.wallet.mktx(outputs,
                                  self.password,
                                  self.config,
                                  domain=self.fund_domain,
                                  change_addr=self.fund_change_address)
            show_transaction(tx,
                             self.main_window,
                             "Notification service payment",
                             prompt_if_unsaved=True)

        self.plugin.switch_to(Intro, self.wallet_name, None, None)

    def wait_for_coin(self, id, timeout=10):
        for j in range(timeout):
            coins = self.wallet.get_spendable_coins(None, self.config)
            for c in coins:
                if c.get('prevout_hash') == id:
                    if c.get('value') == self.value + 190:
                        return c
            time.sleep(1)
            print("Waiting for coin: " + str(j) + "s")
        return None
Ejemplo 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
Ejemplo 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.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)
Ejemplo n.º 4
0
class FundDialog(QDialog, MessageBoxMixin, PrintError):
    def __init__(self, parent, plugin, wallet_name, password, tab):
        QDialog.__init__(self, parent)
        self.main_window = parent
        self.password = password
        self.wallet = parent.wallet
        self.plugin = plugin
        self.network = parent.network
        self.wallet_name = wallet_name
        self.distributions = [
            "Regular Distribution", "TF Distribution", "TF-100", "TF-1000",
            "TF-N"
        ]
        vbox = QVBoxLayout()
        self.setLayout(vbox)
        self.tab = Weak.ref(tab)
        self.total_amount = 0
        self.values = []
        self.distributions_combo = QComboBox()
        self.distributions_combo.addItems(self.distributions)
        self.distributions_combo.setCurrentIndex(0)
        self.selected_distribution = 0

        self.distributions_combo.currentIndexChanged.connect(
            self.on_distribution)

        l = QLabel("<b>%s</b>" % (_("Fund Biletoj (preview)")))
        vbox.addWidget(l)
        vbox.addWidget(self.distributions_combo)
        self.total_amount_wid = BTCAmountEdit(
            self.main_window.get_decimal_point)

        self.total_amount_wid.textEdited.connect(self.fund_parameters_changed)
        vbox.addWidget(self.total_amount_wid)
        self.total_amount_wid.setMaximumWidth(70)

        grid = QGridLayout()
        vbox.addLayout(grid)

        grid.addWidget(QLabel("Average: "), 0, 0)
        grid.addWidget(QLabel("Standard deviation: "), 1, 0)
        grid.addWidget(QLabel("Max: "), 2, 0)
        grid.addWidget(QLabel("Min: "), 3, 0)
        grid.addWidget(QLabel("Sum: "), 4, 0)
        self.stats = [QLabel('') for r in range(5)]
        #print(self.stats)
        for i, lab in enumerate(self.stats):
            grid.addWidget(lab, i, 1)
        tab = self.tab()
        self.selected = tab.tu.selectedItems()
        selected = self.selected
        if not selected:
            print("nothing was selected")
            self.closeEvent()
            return
        if isinstance(selected[0].data(1, Qt.UserRole), Address):
            self.addresses = [s.data(1, Qt.UserRole) for s in selected]
        else:
            self.addresses = selected[0].data(1, Qt.UserRole)

        self.p = QPushButton(_("Preview"))
        self.p.clicked.connect(lambda: self.do_fund(preview=True))
        hbox = QHBoxLayout()
        hbox.addWidget(self.p)
        self.b = QPushButton(_("Fund"))
        self.b.clicked.connect(self.do_fund)
        hbox.addWidget(self.b)
        vbox.addLayout(hbox)
        self.b.setDisabled(True)
        self.p.setDisabled(True)
        vbox.addStretch(1)

    def on_distribution(self):
        self.selected_distribution = self.distributions_combo.currentIndex()
        self.fund_parameters_changed()

    def fund_parameters_changed(self):
        try:
            self.total_amount = self.total_amount_wid.get_amount()
            self.make_outputs()
            self.update_stats()
        except ValueError:
            self.show_message("Not enough biletoj to use this distribution")
            self.b.setDisabled(True)
            self.p.setDisabled(True)
        except Exception as e:
            self.b.setDisabled(True)
            self.p.setDisabled(True)
            pass
        else:
            self.b.setDisabled(False)
            self.p.setDisabled(False)

    def update_stats(self):
        stats = [0] * 5
        stats[0] = self.mean(self.values)
        stats[1] = self.stdev(self.values)
        stats[2] = max(self.values)
        stats[3] = min(self.values)
        stats[4] = sum(self.values)
        for i, s in enumerate(stats):
            self.stats[i].setText(str(self.main_window.format_amount(s)))

    def mean(self, data):
        return sum(data) / len(data)

    def stdev(self, data):
        if len(data) == 1:
            return 0
        m = self.mean(data)
        dev = [(d - m)**2 for d in data]
        return sqrt(sum(dev) / (len(data) - 1))

    def make_outputs(self):
        addr = copy(self.addresses)
        shuffle(addr)
        if self.selected_distribution == 0:
            outputs = self.regular(addr)
        if self.selected_distribution == 1:
            outputs = self.tf(addr)
        if self.selected_distribution == 2:
            outputs = self.tf100(addr, 100)
        if self.selected_distribution == 3:
            outputs = self.tf100(addr, 1000)
        if self.selected_distribution == 4:
            outputs = self.tfN(addr)
        return outputs

    def do_fund(self, preview=False):
        outputs = self.make_outputs()
        if outputs == []:
            return
        if not outputs:
            self.show_error("Minimum value too low to sweep with bitcoin.com")
            return
        tab = self.tab()
        tab.get_password()
        password = tab.password
        try:
            tx = self.wallet.mktx(outputs, password, tab.main_window.config)
        except NotEnoughFunds:
            return self.show_critical(_("Not enough balance to fund biletoj."))
        except Exception as e:
            return self.show_critical(repr(e))
        #self.main_window.show_message("Click \'broadcast\' to fund biletoj.")
        if preview:
            show_transaction(tx,
                             self.main_window,
                             "Fund biletoj",
                             prompt_if_unsaved=False)
        else:
            try:
                self.main_window.network.broadcast_transaction2(tx)
                self.main_window.show_message("Done.")
            except Exception:
                self.main_window.show_message(
                    "Error. Transaction failed to broadcast.")

    def regular(self, addresses):
        outputs = []
        self.values = []
        am = self.total_amount // len(addresses)
        for a in addresses:
            outputs.append((TYPE_ADDRESS, a, am))
            self.values.append(am)
        if min(self.values) <= 1100:
            return None
        return outputs

    def tf(self, addresses):
        outputs = []
        percentage_amount = [0.01, 0.09, 0.15, 0.75]
        percentage_biletoj = [0.5, 0.25, 0.20, 0.05]
        totals = [(amt * self.total_amount) // ceil(bil * len(addresses))
                  for bil, amt in zip(percentage_biletoj, percentage_amount)]
        done = 0
        self.values = []
        for i, j in zip(percentage_amount, percentage_biletoj):
            working_on = len(addresses) * j
            agroup = done + int(floor(working_on))
            for a in addresses[done:agroup]:
                am = int((self.total_amount * i) // ceil(working_on))
                outputs.append((TYPE_ADDRESS, a, am))
                self.values.append(am)
            done = agroup
        assert sum(totals) <= self.total_amount
        if min(self.values) <= 1100:
            return None
        return outputs

    def tf100(self, addresses, n):
        outputs = []
        if len(addresses) != n:
            raise ValueError()
        percentage_amount = [1 / 3., 1 / 3., 1 / 3.]
        percentage_biletoj = [0.01, 0.1, 0.89]
        totals = [(amt * self.total_amount) // ceil(bil * len(addresses))
                  for bil, amt in zip(percentage_biletoj, percentage_amount)]
        done = 0
        self.values = []
        for i, j in zip(percentage_amount, percentage_biletoj):
            group_length = len(addresses) * j
            agroup = done + int(floor(group_length))
            for a in addresses[done:agroup]:
                am = int((self.total_amount * i) // ceil(group_length))
                outputs.append((TYPE_ADDRESS, a, am))
                self.values.append(am)
            done = agroup
        assert sum(totals) <= self.total_amount
        if min(self.values) <= 1100:
            return None
        return outputs

    def tfN(self, addresses):
        outputs = []
        s = len(addresses)
        percentage_amount = [1 / 3., 1 / 3., 1 / 3.]
        percentage_biletoj = [1. / s, 0.1, 0.9 - (1. / s)]
        totals = [(amt * self.total_amount) // ceil(bil * len(addresses))
                  for bil, amt in zip(percentage_biletoj, percentage_amount)]
        done = 0
        self.values = []
        for i, j in zip(percentage_amount, percentage_biletoj):
            group_length = len(addresses) * j
            agroup = done + int(floor(group_length))
            for a in addresses[done:agroup]:
                am = int((self.total_amount * i) // ceil(group_length))
                outputs.append((TYPE_ADDRESS, a, am))
                self.values.append(am)
            done = agroup
        assert sum(totals) <= self.total_amount
        if min(self.values) <= 1100:
            return None
        return outputs

    def closeEvent(self, QCloseEvent):
        tab = self.tab()
        tab.plugin.on_fund_dialog_closed(self.wallet_name)
Ejemplo n.º 5
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)