def maybe_send_invoice_payment(self, tx: Transaction) -> bool: pr = self._payment_request if pr: tx_hash = tx.hash() invoice_id = pr.get_id() # TODO: Remove the dependence of broadcasting a transaction to pay an invoice on that # invoice being active in the send tab. Until then we assume that broadcasting a # transaction that is not related to the active invoice and it's repercussions, has # been confirmed by the appropriate calling logic. Like `confirm_broadcast_transaction` # in the main window logic. invoice_row = self._account.invoices.get_invoice_for_id(invoice_id) if tx_hash != invoice_row.tx_hash: # Calling logic should have detected this and warned/confirmed with the user. return True if pr.has_expired(): pr.error = _("The invoice has expired") self.payment_request_error_signal.emit(invoice_id, tx_hash) return False if not pr.send_payment(self._account, str(tx)): self.payment_request_error_signal.emit(invoice_id, tx_hash) return False self._account.invoices.set_invoice_paid(invoice_id) self._payment_request = None # On success we broadcast as well, but it is assumed that the merchant also # broadcasts. return True
def remove_signed_transaction(self, tx: Transaction, wallet: AbstractAccount): # must remove signed transactions after a failed broadcast attempt (to unlock utxos) # if it's a re-broadcast attempt (same txid) and we already have a StateDispatched or # StateCleared transaction then *no deletion* should occur tx_hash = tx.hash() signed_tx = wallet.get_transaction(tx_hash, flags=TxFlags.StateSigned) if signed_tx: wallet.delete_transaction(tx_hash)
def __init__(self, account: Optional[AbstractAccount], tx: Transaction, main_window: 'ElectrumWindow', prompt_if_unsaved: bool, payment_request: Optional[PaymentRequest] = None) -> None: '''Transactions in the wallet will show their description. Pass desc to give a description for txs not yet in the wallet. ''' # We want to be a top-level window QDialog.__init__(self, parent=None, flags=Qt.WindowSystemMenuHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint) self.copy_data_ready_signal.connect(self._copy_transaction_ready) self.save_data_ready_signal.connect(self._save_transaction_ready) # Take a copy; it might get updated in the main window by the FX thread. If this # happens during or after a long sign operation the signatures are lost. self.tx = copy.deepcopy(tx) self.tx.context = copy.deepcopy(tx.context) self._tx_hash = tx.hash() self._main_window = main_window self._wallet = main_window._wallet self._account = account self._account_id = account.get_id() if account is not None else None self._payment_request = payment_request self._coin_service = CoinService(self._wallet) self._prompt_if_unsaved = prompt_if_unsaved self._saved = False self.setMinimumWidth(1000) self.setWindowTitle(_("Transaction")) self._monospace_font = QFont(platform.monospace_font) self._change_brush = QBrush( ColorScheme.YELLOW.as_color(background=True)) self._receiving_brush = QBrush( ColorScheme.GREEN.as_color(background=True)) self._broken_brush = QBrush(ColorScheme.RED.as_color(background=True)) form = FormSectionWidget() vbox = QVBoxLayout() vbox.addWidget(form) self.setLayout(vbox) self.tx_hash_e = ButtonsLineEdit() self.tx_hash_e.addButton("qrcode.png", self._on_click_show_tx_hash_qr, _("Show as QR code")) self.tx_hash_e.addButton("copy.png", self._on_click_copy_tx_id, _("Copy to clipboard")) self.tx_hash_e.setReadOnly(True) form.add_row(_("Transaction ID"), self.tx_hash_e, True) self.tx_desc = QLabel() form.add_row(_("Description"), self.tx_desc) self.status_label = QLabel() form.add_row(_('Status'), self.status_label) self.date_label = QLabel() form.add_row(_("Date"), self.date_label) self.amount_label = QLabel() form.add_row(_("Amount"), self.amount_label) self.size_label = QLabel() form.add_row(_("Size"), self.size_label) self.fee_label = QLabel() form.add_row(_("Fee"), self.fee_label) if self.tx.locktime > 0: form.add_row(_("Lock time"), QLabel(str(self.tx.locktime))) self._add_io(vbox) self.sign_button = b = QPushButton(_("Sign")) b.clicked.connect(self.sign) self.broadcast_button = b = QPushButton(_("Broadcast")) b.clicked.connect(self.do_broadcast) self.cancel_button = b = QPushButton(_("Close")) b.clicked.connect(self.close) b.setDefault(True) self.qr_button = b = QPushButton() b.setIcon(read_QIcon("qrcode.png")) b.clicked.connect(self._show_qr) self._copy_menu = QMenu() self._copy_button = QPushButton(_("Copy")) self._copy_button.setMenu(self._copy_menu) self._save_menu = QMenu() self.save_button = QPushButton(_("Save")) self.save_button.setMenu(self._save_menu) self.cosigner_button = b = QPushButton(_("Send to cosigner")) b.clicked.connect(self.cosigner_send) # Action buttons self.buttons = [ self.cosigner_button, self.sign_button, self.broadcast_button, self.cancel_button ] # Transaction sharing buttons self.sharing_buttons = [ self._copy_button, self.qr_button, self.save_button ] hbox = QHBoxLayout() hbox.addLayout(Buttons(*self.sharing_buttons)) hbox.addStretch(1) hbox.addLayout(Buttons(*self.buttons)) vbox.addLayout(hbox) self.update() # connect slots so we update in realtime as blocks come in, etc main_window.history_updated_signal.connect(self.update_tx_if_in_wallet) main_window.network_signal.connect(self._on_transaction_verified) main_window.transaction_added_signal.connect( self._on_transaction_added)
def get_tx_info(self, tx: Transaction) -> TxInfo: value_delta = 0 can_broadcast = False label = '' fee = height = conf = timestamp = None tx_hash = tx.hash() if tx.is_complete(): entry = self._wallet._transaction_cache.get_cached_entry(tx_hash) metadata = entry.metadata fee = metadata.fee label = self._account.get_transaction_label(tx_hash) value_delta = self._account.get_transaction_delta(tx_hash) if value_delta is None: # When the transaction is fully signed and updated before the delta changes # are committed to the database (pending write). value_delta = 0 if self._account.has_received_transaction(tx_hash): if (entry.flags & TxFlags.StateSettled or entry.flags & TxFlags.StateCleared and metadata.height > 0): chain = app_state.headers.longest_chain() try: header = app_state.headers.header_at_height( chain, metadata.height) timestamp = header.timestamp except MissingHeader: pass if entry.flags & TxFlags.StateSettled: height = metadata.height conf = max( self._wallet.get_local_height() - height + 1, 0) status = _( "{:,d} confirmations (in block {:,d})").format( conf, height) else: status = _('Not verified') else: status = _('Unconfirmed') else: status = _("Signed") can_broadcast = self._wallet._network is not None else: for input in tx.inputs: value_delta -= input.value for output in tx.outputs: # If we know what type of script it is, we sign it's spend (or co-sign it). if output.script_type != ScriptType.NONE: value_delta += output.value s, r = tx.signature_count() status = _("Unsigned") if s == 0 else _( 'Partially signed') + ' (%d/%d)' % (s, r) if value_delta < 0: if fee is not None: amount = value_delta + fee else: amount = value_delta elif value_delta > 0: amount = value_delta else: amount = None return TxInfo(tx_hash, status, label, can_broadcast, amount, fee, height, conf, timestamp)