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
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
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)
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)
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)