integrate PSBT support natively. WIP
This commit is contained in:
@@ -26,8 +26,8 @@
|
||||
import sys
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
import traceback
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt
|
||||
@@ -42,11 +42,11 @@ from electrum.i18n import _
|
||||
from electrum.plugin import run_hook
|
||||
from electrum import simple_config
|
||||
from electrum.util import bfh
|
||||
from electrum.transaction import SerializationError, Transaction
|
||||
from electrum.transaction import SerializationError, Transaction, PartialTransaction, PartialTxInput
|
||||
from electrum.logging import get_logger
|
||||
|
||||
from .util import (MessageBoxMixin, read_QIcon, Buttons, CopyButton,
|
||||
MONOSPACE_FONT, ColorScheme, ButtonsLineEdit)
|
||||
MONOSPACE_FONT, ColorScheme, ButtonsLineEdit, text_dialog)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .main_window import ElectrumWindow
|
||||
@@ -60,9 +60,9 @@ _logger = get_logger(__name__)
|
||||
dialogs = [] # Otherwise python randomly garbage collects the dialogs...
|
||||
|
||||
|
||||
def show_transaction(tx, parent, *, invoice=None, desc=None, prompt_if_unsaved=False):
|
||||
def show_transaction(tx: Transaction, *, parent: 'ElectrumWindow', invoice=None, desc=None, prompt_if_unsaved=False):
|
||||
try:
|
||||
d = TxDialog(tx, parent, invoice, desc, prompt_if_unsaved)
|
||||
d = TxDialog(tx, parent=parent, invoice=invoice, desc=desc, prompt_if_unsaved=prompt_if_unsaved)
|
||||
except SerializationError as e:
|
||||
_logger.exception('unable to deserialize the transaction')
|
||||
parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e))
|
||||
@@ -73,7 +73,7 @@ def show_transaction(tx, parent, *, invoice=None, desc=None, prompt_if_unsaved=F
|
||||
|
||||
class TxDialog(QDialog, MessageBoxMixin):
|
||||
|
||||
def __init__(self, tx: Transaction, parent: 'ElectrumWindow', invoice, desc, prompt_if_unsaved):
|
||||
def __init__(self, tx: Transaction, *, parent: 'ElectrumWindow', invoice, desc, prompt_if_unsaved):
|
||||
'''Transactions in the wallet will show their description.
|
||||
Pass desc to give a description for txs not yet in the wallet.
|
||||
'''
|
||||
@@ -97,7 +97,7 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
# if the wallet can populate the inputs with more info, do it now.
|
||||
# as a result, e.g. we might learn an imported address tx is segwit,
|
||||
# in which case it's ok to display txid
|
||||
tx.add_inputs_info(self.wallet)
|
||||
tx.add_info_from_wallet(self.wallet)
|
||||
|
||||
self.setMinimumWidth(950)
|
||||
self.setWindowTitle(_("Transaction"))
|
||||
@@ -123,6 +123,9 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
self.broadcast_button = b = QPushButton(_("Broadcast"))
|
||||
b.clicked.connect(self.do_broadcast)
|
||||
|
||||
self.merge_sigs_button = b = QPushButton(_("Merge sigs from"))
|
||||
b.clicked.connect(self.merge_sigs)
|
||||
|
||||
self.save_button = b = QPushButton(_("Save"))
|
||||
save_button_disabled = not tx.is_complete()
|
||||
b.setDisabled(save_button_disabled)
|
||||
@@ -152,7 +155,10 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
self.export_actions_button.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
# Action buttons
|
||||
self.buttons = [self.sign_button, self.broadcast_button, self.cancel_button]
|
||||
self.buttons = []
|
||||
if isinstance(tx, PartialTransaction):
|
||||
self.buttons.append(self.merge_sigs_button)
|
||||
self.buttons += [self.sign_button, self.broadcast_button, self.cancel_button]
|
||||
# Transaction sharing buttons
|
||||
self.sharing_buttons = [self.export_actions_button, self.save_button]
|
||||
|
||||
@@ -190,7 +196,7 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
self.close()
|
||||
|
||||
def show_qr(self):
|
||||
text = bfh(str(self.tx))
|
||||
text = self.tx.serialize_as_bytes()
|
||||
text = base_encode(text, base=43)
|
||||
try:
|
||||
self.main_window.show_qrcode(text, 'Transaction', parent=self)
|
||||
@@ -222,15 +228,43 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
self.saved = True
|
||||
self.main_window.pop_top_level_window(self)
|
||||
|
||||
|
||||
def export(self):
|
||||
name = 'signed_%s.txn' % (self.tx.txid()[0:8]) if self.tx.is_complete() else 'unsigned.txn'
|
||||
fileName = self.main_window.getSaveFileName(_("Select where to save your signed transaction"), name, "*.txn")
|
||||
if fileName:
|
||||
if isinstance(self.tx, PartialTransaction):
|
||||
self.tx.finalize_psbt()
|
||||
if self.tx.is_complete():
|
||||
name = 'signed_%s.txn' % (self.tx.txid()[0:8])
|
||||
else:
|
||||
name = self.wallet.basename() + time.strftime('-%Y%m%d-%H%M.psbt')
|
||||
fileName = self.main_window.getSaveFileName(_("Select where to save your signed transaction"), name, "*.txn;;*.psbt")
|
||||
if not fileName:
|
||||
return
|
||||
if self.tx.is_complete(): # network tx hex
|
||||
with open(fileName, "w+") as f:
|
||||
f.write(json.dumps(self.tx.as_dict(), indent=4) + '\n')
|
||||
self.show_message(_("Transaction exported successfully"))
|
||||
self.saved = True
|
||||
network_tx_hex = self.tx.serialize_to_network()
|
||||
f.write(network_tx_hex + '\n')
|
||||
else: # if partial: PSBT bytes
|
||||
assert isinstance(self.tx, PartialTransaction)
|
||||
with open(fileName, "wb+") as f:
|
||||
f.write(self.tx.serialize_as_bytes())
|
||||
|
||||
self.show_message(_("Transaction exported successfully"))
|
||||
self.saved = True
|
||||
|
||||
def merge_sigs(self):
|
||||
if not isinstance(self.tx, PartialTransaction):
|
||||
return
|
||||
text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
|
||||
if not text:
|
||||
return
|
||||
tx = self.main_window.tx_from_text(text)
|
||||
if not tx:
|
||||
return
|
||||
try:
|
||||
self.tx.combine_with_other_psbt(tx)
|
||||
except Exception as e:
|
||||
self.show_error(_("Error combining partial transactions") + ":\n" + repr(e))
|
||||
return
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
desc = self.desc
|
||||
@@ -319,19 +353,19 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
i_text.setFont(QFont(MONOSPACE_FONT))
|
||||
i_text.setReadOnly(True)
|
||||
cursor = i_text.textCursor()
|
||||
for x in self.tx.inputs():
|
||||
if x['type'] == 'coinbase':
|
||||
for txin in self.tx.inputs():
|
||||
if txin.is_coinbase():
|
||||
cursor.insertText('coinbase')
|
||||
else:
|
||||
prevout_hash = x.get('prevout_hash')
|
||||
prevout_n = x.get('prevout_n')
|
||||
prevout_hash = txin.prevout.txid.hex()
|
||||
prevout_n = txin.prevout.out_idx
|
||||
cursor.insertText(prevout_hash + ":%-4d " % prevout_n, ext)
|
||||
addr = self.wallet.get_txin_address(x)
|
||||
addr = self.wallet.get_txin_address(txin)
|
||||
if addr is None:
|
||||
addr = ''
|
||||
cursor.insertText(addr, text_format(addr))
|
||||
if x.get('value'):
|
||||
cursor.insertText(format_amount(x['value']), ext)
|
||||
if isinstance(txin, PartialTxInput) and txin.value_sats() is not None:
|
||||
cursor.insertText(format_amount(txin.value_sats()), ext)
|
||||
cursor.insertBlock()
|
||||
|
||||
vbox.addWidget(i_text)
|
||||
@@ -340,8 +374,8 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
o_text.setFont(QFont(MONOSPACE_FONT))
|
||||
o_text.setReadOnly(True)
|
||||
cursor = o_text.textCursor()
|
||||
for o in self.tx.get_outputs_for_UI():
|
||||
addr, v = o.address, o.value
|
||||
for o in self.tx.outputs():
|
||||
addr, v = o.get_ui_address_str(), o.value
|
||||
cursor.insertText(addr, text_format(addr))
|
||||
if v is not None:
|
||||
cursor.insertText('\t', ext)
|
||||
|
||||
Reference in New Issue
Block a user