plugins: psbt_nostr: implement for qml
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"fullname": "Nostr Multisig",
|
||||
"description": "This plugin facilitates the use of multi-signatures wallets. It sends and receives partially signed transactions from/to your cosigner wallet. PSBTs are sent and retrieved from Nostr relays.",
|
||||
"author": "The Electrum Developers",
|
||||
"available_for": ["qt"],
|
||||
"icon":"nostr_multisig.png",
|
||||
"available_for": ["qt", "qml"],
|
||||
"version": "0.0.1"
|
||||
}
|
||||
|
||||
@@ -203,6 +203,9 @@ class CosignerWallet(Logger):
|
||||
# note that tx could also be unrelated from wallet?... (not ismine inputs)
|
||||
return True
|
||||
|
||||
def mark_event_rcvd(self, event_id):
|
||||
self.known_events[event_id] = now()
|
||||
|
||||
def prepare_messages(self, tx: Union[Transaction, PartialTransaction]) -> List[Tuple[str, str]]:
|
||||
messages = []
|
||||
for xpub, pubkey in self.cosigner_list:
|
||||
|
||||
@@ -22,10 +22,19 @@
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
from typing import TYPE_CHECKING
|
||||
import asyncio
|
||||
import concurrent
|
||||
from typing import TYPE_CHECKING, List, Tuple, Optional
|
||||
|
||||
from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
|
||||
|
||||
from electrum import util
|
||||
from electrum.plugin import hook
|
||||
from electrum.transaction import PartialTransaction, tx_from_any
|
||||
from electrum.wallet import Multisig_Wallet
|
||||
from electrum.util import EventListener, event_listener
|
||||
|
||||
from electrum.gui.qml.qewallet import QEWallet
|
||||
|
||||
from .psbt_nostr import PsbtNostrPlugin, CosignerWallet
|
||||
|
||||
@@ -34,40 +43,101 @@ if TYPE_CHECKING:
|
||||
from electrum.gui.qml import ElectrumQmlApplication
|
||||
|
||||
|
||||
class QReceiveSignalObject(QObject):
|
||||
def __init__(self, plugin: 'Plugin'):
|
||||
QObject.__init__(self)
|
||||
self._plugin = plugin
|
||||
|
||||
cosignerReceivedPsbt = pyqtSignal(str, str, str)
|
||||
sendPsbtFailed = pyqtSignal(str, arguments=['reason'])
|
||||
sendPsbtSuccess = pyqtSignal()
|
||||
|
||||
@pyqtProperty(str)
|
||||
def loader(self):
|
||||
return 'main.qml'
|
||||
|
||||
@pyqtSlot(QEWallet, str)
|
||||
def sendPsbt(self, wallet: 'QEWallet', tx: str):
|
||||
cosigner_wallet = self._plugin.cosigner_wallets[wallet.wallet]
|
||||
if not cosigner_wallet:
|
||||
return
|
||||
cosigner_wallet.send_psbt(tx_from_any(tx, deserialize=True))
|
||||
|
||||
@pyqtSlot(QEWallet, str)
|
||||
def acceptPsbt(self, wallet: 'QEWallet', event_id: str):
|
||||
cosigner_wallet = self._plugin.cosigner_wallets[wallet.wallet]
|
||||
if not cosigner_wallet:
|
||||
return
|
||||
cosigner_wallet.accept_psbt(event_id)
|
||||
|
||||
|
||||
class Plugin(PsbtNostrPlugin):
|
||||
def __init__(self, parent, config, name):
|
||||
super().__init__(parent, config, name)
|
||||
self.so = QReceiveSignalObject(self)
|
||||
self._app = None
|
||||
|
||||
@hook
|
||||
def init_qml(self, app: 'ElectrumQmlApplication'):
|
||||
# if self._init_qt_received: # only need/want the first signal
|
||||
# return
|
||||
# self._init_qt_received = True
|
||||
self._app = app
|
||||
# plugin enable for already open wallets
|
||||
for wallet in app.daemon.get_wallets():
|
||||
self.so.setParent(app) # parent in QObject tree
|
||||
# plugin enable for already open wallet
|
||||
wallet = app.daemon.currentWallet.wallet if app.daemon.currentWallet else None
|
||||
if wallet:
|
||||
self.load_wallet(wallet)
|
||||
|
||||
@hook
|
||||
def load_wallet(self, wallet: 'Abstract_Wallet'):
|
||||
# remove existing, only foreground wallet active
|
||||
if len(self.cosigner_wallets):
|
||||
self.remove_cosigner_wallet(self.cosigner_wallets[0])
|
||||
if not isinstance(wallet, Multisig_Wallet):
|
||||
return
|
||||
self.add_cosigner_wallet(wallet, CosignerWallet(wallet))
|
||||
self.add_cosigner_wallet(wallet, QmlCosignerWallet(wallet, self))
|
||||
|
||||
# @hook
|
||||
# def on_close_window(self, window):
|
||||
# wallet = window.wallet
|
||||
# self.remove_cosigner_wallet(wallet)
|
||||
#
|
||||
# @hook
|
||||
# def transaction_dialog(self, d: 'TxDialog'):
|
||||
# if cw := self.cosigner_wallets.get(d.wallet):
|
||||
# assert isinstance(cw, QtCosignerWallet)
|
||||
# cw.hook_transaction_dialog(d)
|
||||
#
|
||||
# @hook
|
||||
# def transaction_dialog_update(self, d: 'TxDialog'):
|
||||
# if cw := self.cosigner_wallets.get(d.wallet):
|
||||
# assert isinstance(cw, QtCosignerWallet)
|
||||
# cw.hook_transaction_dialog_update(d)
|
||||
|
||||
class QmlCosignerWallet(EventListener, CosignerWallet):
|
||||
|
||||
def __init__(self, wallet: 'Multisig_Wallet', plugin: 'Plugin'):
|
||||
CosignerWallet.__init__(self, wallet)
|
||||
self.plugin = plugin
|
||||
self.register_callbacks()
|
||||
|
||||
self.pending = None
|
||||
|
||||
@event_listener
|
||||
def on_event_psbt_nostr_received(self, wallet, pubkey, event, tx: 'PartialTransaction'):
|
||||
if self.wallet == wallet:
|
||||
self.plugin.so.cosignerReceivedPsbt.emit(pubkey, event, tx.serialize())
|
||||
self.on_receive(pubkey, event, tx)
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
self.unregister_callbacks()
|
||||
|
||||
def do_send(self, messages: List[Tuple[str, str]], txid: Optional[str] = None):
|
||||
if not messages:
|
||||
return
|
||||
coro = self.send_direct_messages(messages)
|
||||
|
||||
loop = util.get_asyncio_loop()
|
||||
assert util.get_running_loop() != loop, 'must not be called from asyncio thread'
|
||||
self._result = None
|
||||
self._future = asyncio.run_coroutine_threadsafe(coro, loop)
|
||||
|
||||
try:
|
||||
self._result = self._future.result()
|
||||
self.plugin.so.sendPsbtSuccess.emit()
|
||||
except concurrent.futures.CancelledError:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.plugin.so.sendPsbtFailed.emit(str(e))
|
||||
|
||||
def on_receive(self, pubkey, event_id, tx):
|
||||
self.pending = (pubkey, event_id, tx)
|
||||
|
||||
def accept_psbt(self, my_event_id):
|
||||
pubkey, event_id, tx = self.pending
|
||||
if event_id == my_event_id:
|
||||
self.mark_event_rcvd(event_id)
|
||||
self.pending = None
|
||||
|
||||
54
electrum/plugins/psbt_nostr/qml/main.qml
Normal file
54
electrum/plugins/psbt_nostr/qml/main.qml
Normal file
@@ -0,0 +1,54 @@
|
||||
import QtQuick
|
||||
|
||||
import org.electrum
|
||||
|
||||
import "../../../gui/qml/components/controls"
|
||||
|
||||
Item {
|
||||
Connections {
|
||||
target: AppController ? AppController.plugin('psbt_nostr') : null
|
||||
function onCosignerReceivedPsbt(pubkey, event, tx) {
|
||||
var dialog = app.messageDialog.createObject(app, {
|
||||
text: [
|
||||
qsTr('A transaction was received from your cosigner.'),
|
||||
qsTr('Do you want to open it now?')
|
||||
].join('\n'),
|
||||
yesno: true
|
||||
})
|
||||
dialog.accepted.connect(function () {
|
||||
app.stack.push(Qt.resolvedUrl('../../../gui/qml/components/TxDetails.qml'), {
|
||||
rawtx: tx
|
||||
})
|
||||
target.acceptPsbt(Daemon.currentWallet, event)
|
||||
})
|
||||
dialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
property variant export_tx_button: Component {
|
||||
FlatButton {
|
||||
id: psbt_nostr_send_button
|
||||
property variant dialog
|
||||
text: qsTr('Nostr')
|
||||
icon.source: Qt.resolvedUrl('../../../gui/icons/network.png')
|
||||
visible: Daemon.currentWallet.isMultisig && Daemon.currentWallet.walletType != '2fa'
|
||||
onClicked: {
|
||||
console.log('about to psbt nostr send')
|
||||
psbt_nostr_send_button.enabled = false
|
||||
AppController.plugin('psbt_nostr').sendPsbt(Daemon.currentWallet, dialog.text)
|
||||
}
|
||||
Connections {
|
||||
target: AppController ? AppController.plugin('psbt_nostr') : null
|
||||
function onSendPsbtFailed(message) {
|
||||
psbt_nostr_send_button.enabled = true
|
||||
var dialog = app.messageDialog.createObject(app, {
|
||||
text: qsTr('Sending PSBT to co-signer failed:\n%1').arg(message)
|
||||
})
|
||||
dialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,10 +34,9 @@ from electrum.wallet import Multisig_Wallet, Abstract_Wallet
|
||||
from electrum.util import UserCancelled, event_listener, EventListener
|
||||
from electrum.gui.qt.transaction_dialog import show_transaction, TxDialog
|
||||
|
||||
from .psbt_nostr import PsbtNostrPlugin, CosignerWallet, now
|
||||
from .psbt_nostr import PsbtNostrPlugin, CosignerWallet
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from electrum.gui.qt import ElectrumGui
|
||||
from electrum.gui.qt.main_window import ElectrumWindow
|
||||
|
||||
|
||||
@@ -133,5 +132,5 @@ class QtCosignerWallet(EventListener, CosignerWallet):
|
||||
_("An transaction was received from your cosigner.") + '\n' +
|
||||
_("Do you want to open it now?")):
|
||||
return
|
||||
self.known_events[event_id] = now()
|
||||
self.mark_event_rcvd(event_id)
|
||||
show_transaction(tx, parent=window, prompt_if_unsaved=True)
|
||||
|
||||
Reference in New Issue
Block a user