You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
7.7 KiB
185 lines
7.7 KiB
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl |
|
|
|
from typing import Optional, TYPE_CHECKING, Sequence, List, Union |
|
|
|
from electrum.i18n import _ |
|
from electrum.util import register_callback, Satoshis, format_time |
|
from electrum.logging import get_logger |
|
from electrum.wallet import Wallet, Abstract_Wallet |
|
from electrum import bitcoin |
|
from electrum.transaction import PartialTxOutput |
|
from electrum.invoices import (Invoice, InvoiceError, PR_TYPE_ONCHAIN, PR_TYPE_LN, |
|
PR_DEFAULT_EXPIRATION_WHEN_CREATING, PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_UNCONFIRMED, PR_TYPE_ONCHAIN, PR_TYPE_LN) |
|
|
|
from .qerequestlistmodel import QERequestListModel |
|
from .qetransactionlistmodel import QETransactionListModel |
|
from .qeaddresslistmodel import QEAddressListModel |
|
|
|
class QEWallet(QObject): |
|
def __init__(self, wallet, parent=None): |
|
super().__init__(parent) |
|
self.wallet = wallet |
|
self._historyModel = QETransactionListModel(wallet) |
|
self._addressModel = QEAddressListModel(wallet) |
|
self._requestModel = QERequestListModel(wallet) |
|
|
|
self._historyModel.init_model() |
|
self._requestModel.init_model() |
|
|
|
register_callback(self.on_request_status, ['request_status']) |
|
register_callback(self.on_status, ['status']) |
|
|
|
_logger = get_logger(__name__) |
|
|
|
dataChanged = pyqtSignal() # dummy to silence warnings |
|
|
|
requestCreateSuccess = pyqtSignal() |
|
requestCreateError = pyqtSignal([str], arguments=['error']) |
|
|
|
requestStatus = pyqtSignal() |
|
def on_request_status(self, event, *args): |
|
self._logger.debug(str(event)) |
|
self.requestStatus.emit() |
|
|
|
historyModelChanged = pyqtSignal() |
|
@pyqtProperty(QETransactionListModel, notify=historyModelChanged) |
|
def historyModel(self): |
|
return self._historyModel |
|
|
|
addressModelChanged = pyqtSignal() |
|
@pyqtProperty(QEAddressListModel, notify=addressModelChanged) |
|
def addressModel(self): |
|
return self._addressModel |
|
|
|
requestModelChanged = pyqtSignal() |
|
@pyqtProperty(QERequestListModel, notify=requestModelChanged) |
|
def requestModel(self): |
|
return self._requestModel |
|
|
|
@pyqtProperty('QString', notify=dataChanged) |
|
def txinType(self): |
|
return self.wallet.get_txin_type(self.wallet.dummy_address()) |
|
|
|
@pyqtProperty(bool, notify=dataChanged) |
|
def isWatchOnly(self): |
|
return self.wallet.is_watching_only() |
|
|
|
@pyqtProperty(bool, notify=dataChanged) |
|
def isDeterministic(self): |
|
return self.wallet.is_deterministic() |
|
|
|
@pyqtProperty(bool, notify=dataChanged) |
|
def isEncrypted(self): |
|
return self.wallet.storage.is_encrypted() |
|
|
|
@pyqtProperty(bool, notify=dataChanged) |
|
def isHardware(self): |
|
return self.wallet.storage.is_encrypted_with_hw_device() |
|
|
|
@pyqtProperty('QString', notify=dataChanged) |
|
def derivationPath(self): |
|
keystores = self.wallet.get_keystores() |
|
if len(keystores) > 1: |
|
self._logger.debug('multiple keystores not supported yet') |
|
return keystores[0].get_derivation_prefix() |
|
|
|
balanceChanged = pyqtSignal() |
|
|
|
@pyqtProperty(int, notify=balanceChanged) |
|
def frozenBalance(self): |
|
return self.wallet.get_frozen_balance() |
|
|
|
@pyqtProperty(int, notify=balanceChanged) |
|
def unconfirmedBalance(self): |
|
return self.wallet.get_balance()[1] |
|
|
|
@pyqtProperty(int, notify=balanceChanged) |
|
def confirmedBalance(self): |
|
c, u, x = self.wallet.get_balance() |
|
self._logger.info('balance: ' + str(c) + ' ' + str(u) + ' ' + str(x) + ' ') |
|
|
|
return c+x |
|
|
|
def on_status(self, status): |
|
self._logger.info('wallet: status update: ' + str(status)) |
|
self.isUptodateChanged.emit() |
|
|
|
# lightning feature? |
|
isUptodateChanged = pyqtSignal() |
|
@pyqtProperty(bool, notify=isUptodateChanged) |
|
def isUptodate(self): |
|
return self.wallet.is_up_to_date() |
|
|
|
@pyqtSlot('QString', int, int, bool) |
|
def send_onchain(self, address, amount, fee=None, rbf=False): |
|
self._logger.info('send_onchain: ' + address + ' ' + str(amount)) |
|
coins = self.wallet.get_spendable_coins(None) |
|
if not bitcoin.is_address(address): |
|
self._logger.warning('Invalid Bitcoin Address: ' + address) |
|
return False |
|
|
|
outputs = [PartialTxOutput.from_address_and_value(address, amount)] |
|
tx = self.wallet.make_unsigned_transaction(coins=coins,outputs=outputs) |
|
return True |
|
|
|
def create_bitcoin_request(self, amount: int, message: str, expiration: int) -> Optional[str]: |
|
addr = self.wallet.get_unused_address() |
|
if addr is None: |
|
# TODO implement |
|
return |
|
#if not self.wallet.is_deterministic(): # imported wallet |
|
#msg = [ |
|
#_('No more addresses in your wallet.'), ' ', |
|
#_('You are using a non-deterministic wallet, which cannot create new addresses.'), ' ', |
|
#_('If you want to create new addresses, use a deterministic wallet instead.'), '\n\n', |
|
#_('Creating a new payment request will reuse one of your addresses and overwrite an existing request. Continue anyway?'), |
|
#] |
|
#if not self.question(''.join(msg)): |
|
#return |
|
#addr = self.wallet.get_receiving_address() |
|
#else: # deterministic wallet |
|
#if not self.question(_("Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?")): |
|
#return |
|
#addr = self.wallet.create_new_address(False) |
|
req = self.wallet.make_payment_request(addr, amount, message, expiration) |
|
try: |
|
self.wallet.add_payment_request(req) |
|
except Exception as e: |
|
self.logger.exception('Error adding payment request') |
|
self.requestCreateError.emit(_('Error adding payment request') + ':\n' + repr(e)) |
|
else: |
|
# TODO: check this flow. Only if alias is defined in config. OpenAlias? |
|
pass |
|
#self.sign_payment_request(addr) |
|
self._requestModel.add_request(req) |
|
return addr |
|
|
|
@pyqtSlot(int, 'QString', int) |
|
def create_invoice(self, amount: int, message: str, expiration: int, is_lightning: bool = False): |
|
expiry = expiration #TODO: self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING) |
|
try: |
|
if is_lightning: |
|
if not self.wallet.lnworker.channels: |
|
#self.show_error(_("You need to open a Lightning channel first.")) |
|
self.requestCreateError.emit(_("You need to open a Lightning channel first.")) |
|
return |
|
# TODO maybe show a warning if amount exceeds lnworker.num_sats_can_receive (as in kivy) |
|
key = self.wallet.lnworker.add_request(amount, message, expiry) |
|
else: |
|
key = self.create_bitcoin_request(amount, message, expiry) |
|
if not key: |
|
return |
|
#self.address_list.update() |
|
self._addressModel.init_model() |
|
except InvoiceError as e: |
|
self.requestCreateError.emit(_('Error creating payment request') + ':\n' + str(e)) |
|
return |
|
|
|
assert key is not None |
|
self.requestCreateSuccess.emit() |
|
|
|
# TODO:copy to clipboard |
|
#r = self.wallet.get_request(key) |
|
#content = r.invoice if r.is_lightning() else r.get_address() |
|
#title = _('Invoice') if is_lightning else _('Address') |
|
#self.do_copy(content, title=title)
|
|
|