コード例 #1
0
class QERequestListModel(QEAbstractInvoiceListModel):
    def __init__(self, wallet, parent=None):
        super().__init__(wallet, parent)

    _logger = get_logger(__name__)

    def invoice_to_model(self, invoice: Invoice):
        item = super().invoice_to_model(invoice)
        item['type'] = 'request'
        item['key'] = invoice.get_id() if invoice.is_lightning() else invoice.get_address()

        return item

    def get_invoice_list(self):
        return self.wallet.get_unpaid_requests()

    def get_invoice_for_key(self, key: str):
        return self.wallet.get_request(key)

    def get_invoice_as_dict(self, invoice: Invoice):
        return self.wallet.export_request(invoice)

    @pyqtSlot(str, int)
    def updateRequest(self, key, status):
        self.updateInvoice(key, status)
コード例 #2
0
ファイル: qml.py プロジェクト: SomberNight/electrum
class Plugin(BasePlugin):
    def __init__(self, parent, config, name):
        BasePlugin.__init__(self, parent, config, name)

    _logger = get_logger(__name__)

    @hook
    def init_qml(self, gui: 'ElectrumGui'):
        self._logger.debug('init_qml hook called')
コード例 #3
0
class QEWalletListModel(QAbstractListModel):
    _logger = get_logger(__name__)

    def __init__(self, parent=None):
        QAbstractListModel.__init__(self, parent)
        self.wallets = []

    # define listmodel rolemap
    _ROLE_NAMES = ('name', 'path', 'active')
    _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
    _ROLE_MAP = dict(
        zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))

    def rowCount(self, index):
        return len(self.wallets)

    def roleNames(self):
        return self._ROLE_MAP

    def data(self, index, role):
        (wallet_name, wallet_path, wallet) = self.wallets[index.row()]
        role_index = role - Qt.UserRole
        role_name = self._ROLE_NAMES[role_index]
        if role_name == 'name':
            return wallet_name
        if role_name == 'path':
            return wallet_path
        if role_name == 'active':
            return wallet != None

    def add_wallet(self, wallet_path=None, wallet: Abstract_Wallet = None):
        if wallet_path == None and wallet == None:
            return
        # only add wallet instance if instance not yet in model
        if wallet:
            for name, path, w in self.wallets:
                if w == wallet:
                    return
        self.beginInsertRows(QModelIndex(), len(self.wallets),
                             len(self.wallets))
        if wallet == None:
            wallet_name = os.path.basename(wallet_path)
        else:
            wallet_name = wallet.basename()
        item = (wallet_name, wallet_path, wallet)
        self.wallets.append(item)
        self.endInsertRows()
コード例 #4
0
    def __init__(self, qedaemon):
        super().__init__()
        self.logger = get_logger(__name__)

        self._qedaemon = qedaemon

        # set up notification queue and notification_timer
        self.user_notification_queue = queue.Queue()
        self.user_notification_last_time = 0

        self.notification_timer = QTimer(self)
        self.notification_timer.setSingleShot(False)
        self.notification_timer.setInterval(500)  # msec
        self.notification_timer.timeout.connect(self.on_notification_timer)

        self._qedaemon.walletLoaded.connect(self.on_wallet_loaded)

        self.userNotify.connect(self.notifyAndroid)
コード例 #5
0
ファイル: auth.py プロジェクト: SomberNight/electrum
class AuthMixin:
    _auth_logger = get_logger(__name__)

    authRequired = pyqtSignal([str], arguments=['method'])

    @pyqtSlot()
    def authProceed(self):
        self._auth_logger.debug('Proceeding with authed fn()')
        try:
            self._auth_logger.debug(str(getattr(self, '__auth_fcall')))
            (func, args, kwargs, reject) = getattr(self, '__auth_fcall')
            r = func(self, *args, **kwargs)
            return r
        except Exception as e:
            self._auth_logger.error('Error executing wrapped fn(): %s' %
                                    repr(e))
            raise e
        finally:
            delattr(self, '__auth_fcall')

    @pyqtSlot()
    def authCancel(self):
        self._auth_logger.debug('Cancelling authed fn()')
        if not hasattr(self, '__auth_fcall'):
            return

        try:
            (func, args, kwargs, reject) = getattr(self, '__auth_fcall')
            if reject is not None:
                if hasattr(self, reject):
                    getattr(self, reject)()
                else:
                    self._auth_logger.error(
                        'Reject method \'%s\' not defined' % reject)
        except Exception as e:
            self._auth_logger.error(
                'Error executing reject function \'%s\': %s' %
                (reject, repr(e)))
            raise e
        finally:
            delattr(self, '__auth_fcall')
コード例 #6
0
class QEInvoiceListModel(QEAbstractInvoiceListModel):
    def __init__(self, wallet, parent=None):
        super().__init__(wallet, parent)

    _logger = get_logger(__name__)

    def invoice_to_model(self, invoice: Invoice):
        item = super().invoice_to_model(invoice)
        item['type'] = 'invoice'
        item['key'] = invoice.get_id()

        return item

    def get_invoice_list(self):
        return self.wallet.get_unpaid_invoices()

    def get_invoice_for_key(self, key: str):
        return self.wallet.get_invoice(key)

    def get_invoice_as_dict(self, invoice: Invoice):
        return self.wallet.export_invoice(invoice)
コード例 #7
0
ファイル: qeqr.py プロジェクト: SomberNight/electrum
class QEQRImageProvider(QQuickImageProvider):
    def __init__(self, max_size, parent=None):
        super().__init__(QQuickImageProvider.Image)
        self._max_size = max_size

    _logger = get_logger(__name__)

    @profiler
    def requestImage(self, qstr, size):
        self._logger.debug('QR requested for %s' % qstr)
        qr = qrcode.QRCode(version=1, border=2)
        qr.add_data(qstr)

        # calculate best box_size
        pixelsize = min(self._max_size, 400)
        modules = 17 + 4 * qr.best_fit()
        qr.box_size = math.floor(pixelsize / (modules + 2 * 2))

        qr.make(fit=True)

        pimg = qr.make_image(fill_color='black', back_color='white')
        self.qimg = ImageQt.ImageQt(pimg)
        return self.qimg, self.qimg.size()
コード例 #8
0
class QETransactionListModel(QAbstractListModel):
    def __init__(self,
                 wallet,
                 parent=None,
                 *,
                 onchain_domain=None,
                 include_lightning=True):
        super().__init__(parent)
        self.wallet = wallet
        self.onchain_domain = onchain_domain
        self.include_lightning = include_lightning
        self.init_model()

    _logger = get_logger(__name__)

    # define listmodel rolemap
    _ROLE_NAMES = ('txid', 'fee_sat', 'height', 'confirmations', 'timestamp',
                   'monotonic_timestamp', 'incoming', 'value', 'balance',
                   'date', 'label', 'txpos_in_block', 'fee', 'inputs',
                   'outputs', 'section', 'type', 'lightning', 'payment_hash',
                   'key')
    _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
    _ROLE_MAP = dict(
        zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
    _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS))

    def rowCount(self, index):
        return len(self.tx_history)

    def roleNames(self):
        return self._ROLE_MAP

    def data(self, index, role):
        tx = self.tx_history[index.row()]
        role_index = role - Qt.UserRole
        value = tx[self._ROLE_NAMES[role_index]]
        if isinstance(value,
                      (bool, list, int, str, QEAmount)) or value is None:
            return value
        if isinstance(value, Satoshis):
            return value.value
        return str(value)

    def clear(self):
        self.beginResetModel()
        self.tx_history = []
        self.endResetModel()

    def tx_to_model(self, tx):
        #self._logger.debug(str(tx))
        item = tx

        item['key'] = item['txid'] if 'txid' in item else item['payment_hash']

        if not 'lightning' in item:
            item['lightning'] = False

        if item['lightning']:
            item['value'] = QEAmount(amount_sat=item['value'].value,
                                     amount_msat=item['amount_msat'])
            item['balance'] = QEAmount(amount_sat=item['balance'].value,
                                       amount_msat=item['amount_msat'])
            if item['type'] == 'payment':
                item['incoming'] = True if item[
                    'direction'] == 'received' else False
            item['confirmations'] = 0
        else:
            item['value'] = QEAmount(amount_sat=item['value'].value)
            item['balance'] = QEAmount(amount_sat=item['balance'].value)

        # newly arriving txs have no (block) timestamp
        # TODO?
        if not item['timestamp']:
            item['timestamp'] = datetime.timestamp(datetime.now())

        txts = datetime.fromtimestamp(item['timestamp'])
        today = datetime.today().replace(hour=0,
                                         minute=0,
                                         second=0,
                                         microsecond=0)

        if (txts > today):
            item['section'] = 'today'
        elif (txts > today - timedelta(days=1)):
            item['section'] = 'yesterday'
        elif (txts > today - timedelta(days=7)):
            item['section'] = 'lastweek'
        elif (txts > today - timedelta(days=31)):
            item['section'] = 'lastmonth'
        else:
            item['section'] = 'older'

        item['date'] = self.format_date_by_section(
            item['section'], datetime.fromtimestamp(item['timestamp']))
        return item

    def format_date_by_section(self, section, date):
        #TODO: l10n
        dfmt = {
            'today': '%H:%M:%S',
            'yesterday': '%H:%M:%S',
            'lastweek': '%a, %H:%M:%S',
            'lastmonth': '%a %d, %H:%M:%S',
            'older': '%G-%m-%d %H:%M:%S'
        }
        if section not in dfmt:
            section = 'older'
        return date.strftime(dfmt[section])

    # initial model data
    @pyqtSlot()
    def init_model(self):
        history = self.wallet.get_full_history(
            onchain_domain=self.onchain_domain,
            include_lightning=self.include_lightning)
        txs = []
        for key, tx in history.items():
            txs.append(self.tx_to_model(tx))

        self.clear()
        self.beginInsertRows(QModelIndex(), 0, len(txs) - 1)
        self.tx_history = txs
        self.tx_history.reverse()
        self.endInsertRows()

    def update_tx(self, txid, info):
        i = 0
        for tx in self.tx_history:
            if 'txid' in tx and tx['txid'] == txid:
                tx['height'] = info.height
                tx['confirmations'] = info.conf
                tx['timestamp'] = info.timestamp
                tx['date'] = self.format_date_by_section(
                    tx['section'], datetime.fromtimestamp(info.timestamp))
                index = self.index(i, 0)
                roles = [
                    self._ROLE_RMAP[x]
                    for x in ['height', 'confirmations', 'timestamp', 'date']
                ]
                self.dataChanged.emit(index, index, roles)
                return
            i = i + 1

    @pyqtSlot(str, str)
    def update_tx_label(self, key, label):
        i = 0
        for tx in self.tx_history:
            if tx['key'] == key:
                tx['label'] = label
                index = self.index(i, 0)
                self.dataChanged.emit(index, index, [self._ROLE_RMAP['label']])
                return
            i = i + 1

    @pyqtSlot(int)
    def updateBlockchainHeight(self, height):
        self._logger.debug('updating height to %d' % height)
        i = 0
        for tx in self.tx_history:
            if 'height' in tx and tx['height'] > 0:
                tx['confirmations'] = height - tx['height'] + 1
                index = self.index(i, 0)
                roles = [self._ROLE_RMAP['confirmations']]
                self.dataChanged.emit(index, index, roles)
            i = i + 1
コード例 #9
0
class QEAddressListModel(QAbstractListModel):
    def __init__(self, wallet, parent=None):
        super().__init__(parent)
        self.wallet = wallet
        self.init_model()

    _logger = get_logger(__name__)

    # define listmodel rolemap
    _ROLE_NAMES = ('type', 'iaddr', 'address', 'label', 'balance', 'numtx',
                   'held')
    _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
    _ROLE_MAP = dict(
        zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))

    def rowCount(self, index):
        return len(self.receive_addresses) + len(self.change_addresses)

    def roleNames(self):
        return self._ROLE_MAP

    def data(self, index, role):
        if index.row() > len(self.receive_addresses) - 1:
            address = self.change_addresses[index.row() -
                                            len(self.receive_addresses)]
        else:
            address = self.receive_addresses[index.row()]
        role_index = role - Qt.UserRole
        value = address[self._ROLE_NAMES[role_index]]
        if isinstance(value,
                      (bool, list, int, str, QEAmount)) or value is None:
            return value
        if isinstance(value, Satoshis):
            return value.value
        return str(value)

    def clear(self):
        self.beginResetModel()
        self.receive_addresses = []
        self.change_addresses = []
        self.endResetModel()

    def addr_to_model(self, address):
        item = {}
        item['address'] = address
        item['numtx'] = self.wallet.adb.get_address_history_len(address)
        item['label'] = self.wallet.get_label(address)
        c, u, x = self.wallet.get_addr_balance(address)
        item['balance'] = QEAmount(amount_sat=c + u + x)
        item['held'] = self.wallet.is_frozen_address(address)
        return item

    # initial model data
    @pyqtSlot()
    def init_model(self):
        r_addresses = self.wallet.get_receiving_addresses()
        c_addresses = self.wallet.get_change_addresses()
        n_addresses = len(r_addresses) + len(c_addresses)

        def insert_row(atype, alist, address, iaddr):
            item = self.addr_to_model(address)
            item['type'] = atype
            item['iaddr'] = iaddr
            alist.append(item)

        self.clear()
        self.beginInsertRows(QModelIndex(), 0, n_addresses - 1)
        i = 0
        for address in r_addresses:
            insert_row('receive', self.receive_addresses, address, i)
            i = i + 1
        i = 0
        for address in c_addresses:
            insert_row('change', self.change_addresses, address, i)
            i = i + 1
        self.endInsertRows()

    @pyqtSlot(str)
    def update_address(self, address):
        i = 0
        for a in self.receive_addresses:
            if a['address'] == address:
                self.do_update(i, a)
                return
            i = i + 1
        for a in self.change_addresses:
            if a['address'] == address:
                self.do_update(i, a)
                return
            i = i + 1

    def do_update(self, modelindex, modelitem):
        mi = self.createIndex(modelindex, 0)
        self._logger.debug(repr(modelitem))
        modelitem.update(self.addr_to_model(modelitem['address']))
        self._logger.debug(repr(modelitem))
        self.dataChanged.emit(mi, mi, self._ROLE_KEYS)
コード例 #10
0
ファイル: qeinvoice.py プロジェクト: SomberNight/electrum
class QEInvoice(QObject):
    class Type:
        Invalid = -1
        OnchainOnlyAddress = 0
        OnchainInvoice = 1
        LightningInvoice = 2
        LightningAndOnchainInvoice = 3

    class Status:
        Unpaid = PR_UNPAID
        Expired = PR_EXPIRED
        Unknown = PR_UNKNOWN
        Paid = PR_PAID
        Inflight = PR_INFLIGHT
        Failed = PR_FAILED
        Routing = PR_ROUTING
        Unconfirmed = PR_UNCONFIRMED

    Q_ENUMS(Type)
    Q_ENUMS(Status)

    _logger = get_logger(__name__)

    _wallet = None
    _canSave = False
    _canPay = False
    _key = None

    def __init__(self, parent=None):
        super().__init__(parent)

    walletChanged = pyqtSignal()
    @pyqtProperty(QEWallet, notify=walletChanged)
    def wallet(self):
        return self._wallet

    @wallet.setter
    def wallet(self, wallet: QEWallet):
        if self._wallet != wallet:
            self._wallet = wallet
            self.walletChanged.emit()

    canSaveChanged = pyqtSignal()
    @pyqtProperty(bool, notify=canSaveChanged)
    def canSave(self):
        return self._canSave

    @canSave.setter
    def canSave(self, canSave):
        if self._canSave != canSave:
            self._canSave = canSave
            self.canSaveChanged.emit()

    canPayChanged = pyqtSignal()
    @pyqtProperty(bool, notify=canPayChanged)
    def canPay(self):
        return self._canPay

    @canPay.setter
    def canPay(self, canPay):
        if self._canPay != canPay:
            self._canPay = canPay
            self.canPayChanged.emit()

    keyChanged = pyqtSignal()
    @pyqtProperty(str, notify=keyChanged)
    def key(self):
        return self._key

    @key.setter
    def key(self, key):
        if self._key != key:
            self._key = key
            self.keyChanged.emit()

    userinfoChanged = pyqtSignal()
    @pyqtProperty(str, notify=userinfoChanged)
    def userinfo(self):
        return self._userinfo

    @userinfo.setter
    def userinfo(self, userinfo):
        if self._userinfo != userinfo:
            self._userinfo = userinfo
            self.userinfoChanged.emit()

    def get_max_spendable_onchain(self):
        c, u, x = self._wallet.wallet.get_balance()
        #TODO determine real max
        return c
コード例 #11
0
ファイル: qeinvoice.py プロジェクト: SomberNight/electrum
class QEUserEnteredPayment(QEInvoice):
    _logger = get_logger(__name__)

    _recipient = None
    _message = None
    _amount = QEAmount()

    validationError = pyqtSignal([str,str], arguments=['code','message'])
    invoiceCreateError = pyqtSignal([str,str], arguments=['code', 'message'])
    invoiceSaved = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.clear()

    recipientChanged = pyqtSignal()
    @pyqtProperty(str, notify=recipientChanged)
    def recipient(self):
        return self._recipient

    @recipient.setter
    def recipient(self, recipient: str):
        if self._recipient != recipient:
            self._recipient = recipient
            self.validate()
            self.recipientChanged.emit()

    messageChanged = pyqtSignal()
    @pyqtProperty(str, notify=messageChanged)
    def message(self):
        return self._message

    @message.setter
    def message(self, message):
        if self._message != message:
            self._message = message
            self.messageChanged.emit()

    amountChanged = pyqtSignal()
    @pyqtProperty(QEAmount, notify=amountChanged)
    def amount(self):
        return self._amount

    @amount.setter
    def amount(self, amount):
        if self._amount != amount:
            self._amount = amount
            self.validate()
            self.amountChanged.emit()


    def validate(self):
        self.canPay = False
        self.canSave = False
        self._logger.debug('validate')

        if not self._recipient:
            self.validationError.emit('recipient', _('Recipient not specified.'))
            return

        if not bitcoin.is_address(self._recipient):
            self.validationError.emit('recipient', _('Invalid Bitcoin address'))
            return

        if self._amount.isEmpty:
            self.validationError.emit('amount', _('Invalid amount'))
            return

        if self._amount.isMax:
            self.canPay = True
        else:
            self.canSave = True
            if self.get_max_spendable_onchain() >= self._amount.satsInt:
                self.canPay = True

    @pyqtSlot()
    def save_invoice(self):
        assert self.canSave
        assert not self._amount.isMax

        self._logger.debug('saving invoice to %s, amount=%s, message=%s' % (self._recipient, repr(self._amount), self._message))

        inv_amt = self._amount.satsInt

        try:
            outputs = [PartialTxOutput.from_address_and_value(self._recipient, inv_amt)]
            self._logger.debug(repr(outputs))
            invoice = self._wallet.wallet.create_invoice(outputs=outputs, message=self._message, pr=None, URI=None)
        except InvoiceError as e:
            self.invoiceCreateError.emit('fatal', _('Error creating payment') + ':\n' + str(e))
            return

        self.key = self._wallet.wallet.get_key_for_outgoing_invoice(invoice)
        self._wallet.wallet.save_invoice(invoice)
        self.invoiceSaved.emit()

    @pyqtSlot()
    def clear(self):
        self._recipient = None
        self._amount = QEAmount()
        self._message = None
        self.canSave = False
        self.canPay = False
コード例 #12
0
class QEFX(QObject, QtEventListener):
    def __init__(self, fxthread: FxThread, config: SimpleConfig, parent=None):
        super().__init__(parent)
        self.fx = fxthread
        self.config = config
        self.register_callbacks()
        self.destroyed.connect(lambda: self.on_destroy())

    _logger = get_logger(__name__)

    quotesUpdated = pyqtSignal()

    def on_destroy(self):
        self.unregister_callbacks()

    @event_listener
    def on_event_on_quotes(self, *args):
        self._logger.debug('new quotes')
        self.quotesUpdated.emit()

    historyUpdated = pyqtSignal()

    @event_listener
    def on_event_on_history(self, *args):
        self._logger.debug('new history')
        self.historyUpdated.emit()

    currenciesChanged = pyqtSignal()

    @pyqtProperty('QVariantList', notify=currenciesChanged)
    def currencies(self):
        return self.fx.get_currencies(self.historicRates)

    rateSourcesChanged = pyqtSignal()

    @pyqtProperty('QVariantList', notify=rateSourcesChanged)
    def rateSources(self):
        return self.fx.get_exchanges_by_ccy(self.fiatCurrency,
                                            self.historicRates)

    fiatCurrencyChanged = pyqtSignal()

    @pyqtProperty(str, notify=fiatCurrencyChanged)
    def fiatCurrency(self):
        return self.fx.get_currency()

    @fiatCurrency.setter
    def fiatCurrency(self, currency):
        if currency != self.fiatCurrency:
            self.fx.set_currency(currency)
            self.enabled = self.enabled and currency != ''
            self.fiatCurrencyChanged.emit()
            self.rateSourcesChanged.emit()

    historicRatesChanged = pyqtSignal()

    @pyqtProperty(bool, notify=historicRatesChanged)
    def historicRates(self):
        return self.fx.get_history_config()

    @historicRates.setter
    def historicRates(self, checked):
        if checked != self.historicRates:
            self.fx.set_history_config(checked)
            self.historicRatesChanged.emit()
            self.rateSourcesChanged.emit()

    rateSourceChanged = pyqtSignal()

    @pyqtProperty(str, notify=rateSourceChanged)
    def rateSource(self):
        return self.fx.config_exchange()

    @rateSource.setter
    def rateSource(self, source):
        if source != self.rateSource:
            self.fx.set_exchange(source)
            self.rateSourceChanged.emit()

    enabledUpdated = pyqtSignal(
    )  # curiously, enabledChanged is clashing, so name it enabledUpdated

    @pyqtProperty(bool, notify=enabledUpdated)
    def enabled(self):
        return self.fx.is_enabled()

    @enabled.setter
    def enabled(self, enable):
        if enable != self.enabled:
            self.fx.set_enabled(enable)
            self.enabledUpdated.emit()

    @pyqtSlot(str, result=str)
    @pyqtSlot(str, bool, result=str)
    @pyqtSlot(QEAmount, result=str)
    @pyqtSlot(QEAmount, bool, result=str)
    def fiatValue(self, satoshis, plain=True):
        rate = self.fx.exchange_rate()
        if isinstance(satoshis, QEAmount):
            satoshis = satoshis.satsInt
        else:
            try:
                sd = Decimal(satoshis)
            except:
                return ''
        if plain:
            return self.fx.ccy_amount_str(self.fx.fiat_value(satoshis, rate),
                                          False)
        else:
            return self.fx.value_str(satoshis, rate)

    @pyqtSlot(str, str, result=str)
    @pyqtSlot(str, str, bool, result=str)
    @pyqtSlot(QEAmount, str, result=str)
    @pyqtSlot(QEAmount, str, bool, result=str)
    def fiatValueHistoric(self, satoshis, timestamp, plain=True):
        if isinstance(satoshis, QEAmount):
            satoshis = satoshis.satsInt
        else:
            try:
                sd = Decimal(satoshis)
            except:
                return ''

        try:
            td = Decimal(timestamp)
            if td == 0:
                return ''
        except:
            return ''
        dt = datetime.fromtimestamp(int(td))
        if plain:
            return self.fx.ccy_amount_str(
                self.fx.historical_value(satoshis, dt), False)
        else:
            return self.fx.historical_value_str(satoshis, dt)

    @pyqtSlot(str, result=str)
    @pyqtSlot(str, bool, result=str)
    def satoshiValue(self, fiat, plain=True):
        rate = self.fx.exchange_rate()
        try:
            fd = Decimal(fiat)
        except:
            return ''
        v = fd / Decimal(rate) * COIN
        if v.is_nan():
            return ''
        if plain:
            return str(v.to_integral_value())
        else:
            return self.config.format_amount(v)
コード例 #13
0
ファイル: qeinvoice.py プロジェクト: SomberNight/electrum
class QEInvoiceParser(QEInvoice):

    _logger = get_logger(__name__)

    _invoiceType = QEInvoice.Type.Invalid
    _recipient = ''
    _effectiveInvoice = None
    _amount = QEAmount()
    _userinfo = ''

    invoiceChanged = pyqtSignal()
    invoiceSaved = pyqtSignal()

    validationSuccess = pyqtSignal()
    validationWarning = pyqtSignal([str,str], arguments=['code', 'message'])
    validationError = pyqtSignal([str,str], arguments=['code', 'message'])

    invoiceCreateError = pyqtSignal([str,str], arguments=['code', 'message'])

    def __init__(self, parent=None):
        super().__init__(parent)
        self.clear()

    @pyqtProperty(int, notify=invoiceChanged)
    def invoiceType(self):
        return self._invoiceType

    # not a qt setter, don't let outside set state
    def setInvoiceType(self, invoiceType: QEInvoice.Type):
        self._invoiceType = invoiceType

    recipientChanged = pyqtSignal()
    @pyqtProperty(str, notify=recipientChanged)
    def recipient(self):
        return self._recipient

    @recipient.setter
    def recipient(self, recipient: str):
        #if self._recipient != recipient:
        self._recipient = recipient
        if recipient:
            self.validateRecipient(recipient)
        self.recipientChanged.emit()

    @pyqtProperty(str, notify=invoiceChanged)
    def message(self):
        return self._effectiveInvoice.message if self._effectiveInvoice else ''

    @pyqtProperty(QEAmount, notify=invoiceChanged)
    def amount(self):
        # store ref to QEAmount on instance, otherwise we get destroyed when going out of scope
        self._amount = QEAmount()
        if not self._effectiveInvoice:
            return self._amount
        self._amount = QEAmount(from_invoice=self._effectiveInvoice)
        return self._amount

    @pyqtProperty('quint64', notify=invoiceChanged)
    def expiration(self):
        return self._effectiveInvoice.exp if self._effectiveInvoice else 0

    @pyqtProperty('quint64', notify=invoiceChanged)
    def time(self):
        return self._effectiveInvoice.time if self._effectiveInvoice else 0

    statusChanged = pyqtSignal()
    @pyqtProperty(int, notify=statusChanged)
    def status(self):
        if not self._effectiveInvoice:
            return PR_UNKNOWN
        return self._wallet.wallet.get_invoice_status(self._effectiveInvoice)

    @pyqtProperty(str, notify=statusChanged)
    def status_str(self):
        if not self._effectiveInvoice:
            return ''
        status = self._wallet.wallet.get_invoice_status(self._effectiveInvoice)
        return self._effectiveInvoice.get_status_str(status)

    # single address only, TODO: n outputs
    @pyqtProperty(str, notify=invoiceChanged)
    def address(self):
        return self._effectiveInvoice.get_address() if self._effectiveInvoice else ''

    @pyqtProperty('QVariantMap', notify=invoiceChanged)
    def lnprops(self):
        if not self.invoiceType == QEInvoice.Type.LightningInvoice:
            return {}
        lnaddr = self._effectiveInvoice._lnaddr
        self._logger.debug(str(lnaddr))
        self._logger.debug(str(lnaddr.get_routing_info('t')))
        return {
            'pubkey': lnaddr.pubkey.serialize().hex(),
            't': '', #lnaddr.get_routing_info('t')[0][0].hex(),
            'r': '' #lnaddr.get_routing_info('r')[0][0][0].hex()
        }

    @pyqtSlot()
    def clear(self):
        self.recipient = ''
        self.setInvoiceType(QEInvoice.Type.Invalid)
        self._bip21 = None
        self.canSave = False
        self.canPay = False
        self.userinfo = ''
        self.invoiceChanged.emit()

    # don't parse the recipient string, but init qeinvoice from an invoice key
    # this should not emit validation signals
    @pyqtSlot(str)
    def initFromKey(self, key):
        self.clear()
        invoice = self._wallet.wallet.get_invoice(key)
        self._logger.debug(repr(invoice))
        if invoice:
            self.set_effective_invoice(invoice)
            self.key = key

    def set_effective_invoice(self, invoice: Invoice):
        self._effectiveInvoice = invoice

        if invoice.is_lightning():
            self.setInvoiceType(QEInvoice.Type.LightningInvoice)
        else:
            self.setInvoiceType(QEInvoice.Type.OnchainInvoice)

        self.canSave = True

        self.determine_can_pay()

        self.invoiceChanged.emit()
        self.statusChanged.emit()

    def determine_can_pay(self):
        if self.invoiceType == QEInvoice.Type.LightningInvoice:
            if self.status in [PR_UNPAID, PR_FAILED]:
                if self.get_max_spendable_lightning() >= self.amount.satsInt:
                    self.canPay = True
                else:
                    self.userinfo = _('Can\'t pay, insufficient balance')
            else:
                self.userinfo = {
                        PR_EXPIRED: _('Can\'t pay, invoice is expired'),
                        PR_PAID: _('Can\'t pay, invoice is already paid'),
                        PR_INFLIGHT: _('Can\'t pay, invoice is already being paid'),
                        PR_ROUTING: _('Can\'t pay, invoice is already being paid'),
                        PR_UNKNOWN: _('Can\'t pay, invoice has unknown status'),
                    }[self.status]
        elif self.invoiceType == QEInvoice.Type.OnchainInvoice:
            if self.status in [PR_UNPAID, PR_FAILED]:
                if self.get_max_spendable_onchain() >= self.amount.satsInt:
                    self.canPay = True
                else:
                    self.userinfo = _('Can\'t pay, insufficient balance')
            else:
                self.userinfo = {
                        PR_EXPIRED: _('Can\'t pay, invoice is expired'),
                        PR_PAID: _('Can\'t pay, invoice is already paid'),
                        PR_UNCONFIRMED: _('Can\'t pay, invoice is already paid'),
                        PR_UNKNOWN: _('Can\'t pay, invoice has unknown status'),
                    }[self.status]


    def get_max_spendable_lightning(self):
        return self._wallet.wallet.lnworker.num_sats_can_send()

    def setValidAddressOnly(self):
        self._logger.debug('setValidAddressOnly')
        self.setInvoiceType(QEInvoice.Type.OnchainOnlyAddress)
        self._effectiveInvoice = None
        self.invoiceChanged.emit()

    def setValidOnchainInvoice(self, invoice: Invoice):
        self._logger.debug('setValidOnchainInvoice')
        if invoice.is_lightning():
            raise Exception('unexpected LN invoice')
        self.set_effective_invoice(invoice)

    def setValidLightningInvoice(self, invoice: Invoice):
        self._logger.debug('setValidLightningInvoice')
        if not invoice.is_lightning():
            raise Exception('unexpected Onchain invoice')
        self.set_effective_invoice(invoice)

    def create_onchain_invoice(self, outputs, message, payment_request, uri):
        return self._wallet.wallet.create_invoice(
            outputs=outputs,
            message=message,
            pr=payment_request,
            URI=uri
            )

    def validateRecipient(self, recipient):
        if not recipient:
            self.setInvoiceType(QEInvoice.Type.Invalid)
            return

        maybe_lightning_invoice = recipient

        def _payment_request_resolved(request):
            self._logger.debug('resolved payment request')
            outputs = request.get_outputs()
            invoice = self.create_onchain_invoice(outputs, None, request, None)
            self.setValidOnchainInvoice(invoice)

        try:
            self._bip21 = parse_URI(recipient, _payment_request_resolved)
            if self._bip21:
                if 'r' in self._bip21 or ('name' in self._bip21 and 'sig' in self._bip21): # TODO set flag in util?
                    # let callback handle state
                    return
                if ':' not in recipient:
                    # address only
                    self.setValidAddressOnly()
                    self.validationSuccess.emit()
                    return
                else:
                    # fallback lightning invoice?
                    if 'lightning' in self._bip21:
                        maybe_lightning_invoice = self._bip21['lightning']
        except InvalidBitcoinURI as e:
            self._bip21 = None
            self._logger.debug(repr(e))

        lninvoice = None
        maybe_lightning_invoice = maybe_extract_lightning_payment_identifier(maybe_lightning_invoice)
        if maybe_lightning_invoice is not None:
            try:
                lninvoice = Invoice.from_bech32(maybe_lightning_invoice)
            except InvoiceError as e:
                e2 = e.__cause__
                if isinstance(e2, LnInvoiceException):
                    self.validationError.emit('unknown', _("Error parsing Lightning invoice") + f":\n{e2}")
                    self.clear()
                    return
                if isinstance(e2, lnutil.IncompatibleOrInsaneFeatures):
                    self.validationError.emit('unknown', _("Invoice requires unknown or incompatible Lightning feature") + f":\n{e2!r}")
                    self.clear()
                    return
                self._logger.exception(repr(e))

        if not lninvoice and not self._bip21:
            self.validationError.emit('unknown',_('Unknown invoice'))
            self.clear()
            return

        if lninvoice:
            if not self._wallet.wallet.has_lightning():
                if not self._bip21:
                    # TODO: lightning onchain fallback in ln invoice
                    #self.validationError.emit('no_lightning',_('Detected valid Lightning invoice, but Lightning not enabled for wallet'))
                    self.setValidLightningInvoice(lninvoice)
                    self.clear()
                    return
                else:
                    self._logger.debug('flow with LN but not LN enabled AND having bip21 uri')
                    self.setValidOnchainInvoice(self._bip21['address'])
            else:
                self.setValidLightningInvoice(lninvoice)
                if not self._wallet.wallet.lnworker.channels:
                    self.validationWarning.emit('no_channels',_('Detected valid Lightning invoice, but there are no open channels'))
                else:
                    self.validationSuccess.emit()
        else:
            self._logger.debug('flow without LN but having bip21 uri')
            if 'amount' not in self._bip21: #TODO can we have amount-less invoices?
                self.validationError.emit('no_amount', 'no amount in uri')
                return
            outputs = [PartialTxOutput.from_address_and_value(self._bip21['address'], self._bip21['amount'])]
            self._logger.debug(outputs)
            message = self._bip21['message'] if 'message' in self._bip21 else ''
            invoice = self.create_onchain_invoice(outputs, message, None, self._bip21)
            self._logger.debug(repr(invoice))
            self.setValidOnchainInvoice(invoice)
            self.validationSuccess.emit()

    @pyqtSlot()
    def save_invoice(self):
        self.canSave = False
        if not self._effectiveInvoice:
            return
        # TODO detect duplicate?
        self.key = self._wallet.wallet.get_key_for_outgoing_invoice(self._effectiveInvoice)
        self._wallet.wallet.save_invoice(self._effectiveInvoice)
        self.invoiceSaved.emit()
コード例 #14
0
class QEAmount(QObject):
    _logger = get_logger(__name__)

    def __init__(self,
                 *,
                 amount_sat: int = 0,
                 amount_msat: int = 0,
                 is_max: bool = False,
                 from_invoice=None,
                 parent=None):
        super().__init__(parent)
        self._amount_sat = int(amount_sat) if amount_sat is not None else None
        self._amount_msat = int(
            amount_msat) if amount_msat is not None else None
        self._is_max = is_max
        if from_invoice:
            inv_amt = from_invoice.get_amount_msat()
            if inv_amt == '!':
                self._is_max = True
            elif inv_amt is not None:
                self._amount_msat = int(inv_amt)
                self._amount_sat = int(from_invoice.get_amount_sat())

    valueChanged = pyqtSignal()

    @pyqtProperty('qint64', notify=valueChanged)
    def satsInt(self):
        return self._amount_sat

    @pyqtProperty('qint64', notify=valueChanged)
    def msatsInt(self):
        return self._amount_msat

    @pyqtProperty(str, notify=valueChanged)
    def satsStr(self):
        return str(self._amount_sat)

    @pyqtProperty(str, notify=valueChanged)
    def msatsStr(self):
        return str(self._amount_msat)

    @pyqtProperty(bool, notify=valueChanged)
    def isMax(self):
        return self._is_max

    @pyqtProperty(bool, notify=valueChanged)
    def isEmpty(self):
        return not (self._is_max or self._amount_sat or self._amount_msat)

    def __eq__(self, other):
        if isinstance(other, QEAmount):
            return self._amount_sat == other._amount_sat and self._amount_msat == other._amount_msat and self._is_max == other._is_max
        elif isinstance(other, int):
            return self._amount_sat == other
        elif isinstance(other, str):
            return self.satsStr == other

        return False

    def __str__(self):
        s = _('Amount')
        if self._is_max:
            return '%s(MAX)' % s
        return '%s(sats=%d, msats=%d)' % (s, self._amount_sat,
                                          self._amount_msat)

    def __repr__(self):
        return f"<QEAmount max={self._is_max} sats={self._amount_sat} msats={self._amount_msat} empty={self.isEmpty}>"
コード例 #15
0
class QETxFinalizer(QObject):
    def __init__(self, parent=None, *, make_tx=None, accept=None):
        super().__init__(parent)
        self.f_make_tx = make_tx
        self.f_accept = accept
        self._tx = None

    _logger = get_logger(__name__)

    _address = ''
    _amount = QEAmount()
    _effectiveAmount = QEAmount()
    _fee = QEAmount()
    _feeRate = ''
    _wallet = None
    _valid = False
    _sliderSteps = 0
    _sliderPos = 0
    _method = -1
    _warning = ''
    _target = ''
    _rbf = False
    _canRbf = False
    _outputs = []
    config = None

    validChanged = pyqtSignal()

    @pyqtProperty(bool, notify=validChanged)
    def valid(self):
        return self._valid

    walletChanged = pyqtSignal()

    @pyqtProperty(QEWallet, notify=walletChanged)
    def wallet(self):
        return self._wallet

    @wallet.setter
    def wallet(self, wallet: QEWallet):
        if self._wallet != wallet:
            self._wallet = wallet
            self.config = self._wallet.wallet.config
            self.read_config()
            self.walletChanged.emit()

    addressChanged = pyqtSignal()

    @pyqtProperty(str, notify=addressChanged)
    def address(self):
        return self._address

    @address.setter
    def address(self, address):
        if self._address != address:
            self._address = address
            self.addressChanged.emit()

    amountChanged = pyqtSignal()

    @pyqtProperty(QEAmount, notify=amountChanged)
    def amount(self):
        return self._amount

    @amount.setter
    def amount(self, amount):
        if self._amount != amount:
            self._logger.debug(str(amount))
            self._amount = amount
            self.amountChanged.emit()

    effectiveAmountChanged = pyqtSignal()

    @pyqtProperty(QEAmount, notify=effectiveAmountChanged)
    def effectiveAmount(self):
        return self._effectiveAmount

    feeChanged = pyqtSignal()

    @pyqtProperty(QEAmount, notify=feeChanged)
    def fee(self):
        return self._fee

    @fee.setter
    def fee(self, fee):
        if self._fee != fee:
            self._fee = fee
            self.feeChanged.emit()

    feeRateChanged = pyqtSignal()

    @pyqtProperty(str, notify=feeRateChanged)
    def feeRate(self):
        return self._feeRate

    @feeRate.setter
    def feeRate(self, feeRate):
        if self._feeRate != feeRate:
            self._feeRate = feeRate
            self.feeRateChanged.emit()

    targetChanged = pyqtSignal()

    @pyqtProperty(str, notify=targetChanged)
    def target(self):
        return self._target

    @target.setter
    def target(self, target):
        if self._target != target:
            self._target = target
            self.targetChanged.emit()

    rbfChanged = pyqtSignal()

    @pyqtProperty(bool, notify=rbfChanged)
    def rbf(self):
        return self._rbf

    @rbf.setter
    def rbf(self, rbf):
        if self._rbf != rbf:
            self._rbf = rbf
            self.update()
            self.rbfChanged.emit()

    canRbfChanged = pyqtSignal()

    @pyqtProperty(bool, notify=canRbfChanged)
    def canRbf(self):
        return self._canRbf

    @canRbf.setter
    def canRbf(self, canRbf):
        if self._canRbf != canRbf:
            self._canRbf = canRbf
            self.canRbfChanged.emit()
            if not canRbf and self.rbf:
                self.rbf = False

    outputsChanged = pyqtSignal()

    @pyqtProperty('QVariantList', notify=outputsChanged)
    def outputs(self):
        return self._outputs

    @outputs.setter
    def outputs(self, outputs):
        if self._outputs != outputs:
            self._outputs = outputs
            self.outputsChanged.emit()

    warningChanged = pyqtSignal()

    @pyqtProperty(str, notify=warningChanged)
    def warning(self):
        return self._warning

    @warning.setter
    def warning(self, warning):
        if self._warning != warning:
            self._warning = warning
            self.warningChanged.emit()

    sliderStepsChanged = pyqtSignal()

    @pyqtProperty(int, notify=sliderStepsChanged)
    def sliderSteps(self):
        return self._sliderSteps

    sliderPosChanged = pyqtSignal()

    @pyqtProperty(int, notify=sliderPosChanged)
    def sliderPos(self):
        return self._sliderPos

    @sliderPos.setter
    def sliderPos(self, sliderPos):
        if self._sliderPos != sliderPos:
            self._sliderPos = sliderPos
            self.save_config()
            self.sliderPosChanged.emit()

    methodChanged = pyqtSignal()

    @pyqtProperty(int, notify=methodChanged)
    def method(self):
        return self._method

    @method.setter
    def method(self, method):
        if self._method != method:
            self._method = method
            self.update_slider()
            self.methodChanged.emit()
            self.save_config()

    def get_method(self):
        dynfees = self._method > 0
        mempool = self._method == 2
        return dynfees, mempool

    def update_slider(self):
        dynfees, mempool = self.get_method()
        maxp, pos, fee_rate = self.config.get_fee_slider(dynfees, mempool)
        self._sliderSteps = maxp
        self._sliderPos = pos
        self.sliderStepsChanged.emit()
        self.sliderPosChanged.emit()

    def read_config(self):
        mempool = self.config.use_mempool_fees()
        dynfees = self.config.is_dynfee()
        self._method = (2 if mempool else 1) if dynfees else 0
        self.update_slider()
        self.methodChanged.emit()
        self.update()

    def save_config(self):
        value = int(self._sliderPos)
        dynfees, mempool = self.get_method()
        self.config.set_key('dynamic_fees', dynfees, False)
        self.config.set_key('mempool_fees', mempool, False)
        if dynfees:
            if mempool:
                self.config.set_key('depth_level', value, True)
            else:
                self.config.set_key('fee_level', value, True)
        else:
            self.config.set_key('fee_per_kb', self.config.static_fee(value),
                                True)
        self.update()

    @profiler
    def make_tx(self, amount):
        self._logger.debug('make_tx amount = %s' % str(amount))

        if self.f_make_tx:
            tx = self.f_make_tx(amount)
        else:
            # default impl
            coins = self._wallet.wallet.get_spendable_coins(None)
            outputs = [
                PartialTxOutput.from_address_and_value(self.address, amount)
            ]
            tx = self._wallet.wallet.make_unsigned_transaction(coins=coins,
                                                               outputs=outputs,
                                                               fee=None,
                                                               rbf=self._rbf)

        self._logger.debug('fee: %d, inputs: %d, outputs: %d' %
                           (tx.get_fee(), len(tx.inputs()), len(tx.outputs())))

        outputs = []
        for o in tx.outputs():
            outputs.append({
                'address':
                o.get_ui_address_str(),
                'value_sats':
                o.value,
                'is_mine':
                self._wallet.wallet.is_mine(o.get_ui_address_str())
            })
        self.outputs = outputs

        return tx

    @pyqtSlot()
    def update(self):
        try:
            # make unsigned transaction
            tx = self.make_tx(
                amount='!' if self._amount.isMax else self._amount.satsInt)
        except NotEnoughFunds:
            self.warning = _("Not enough funds")
            self._valid = False
            self.validChanged.emit()
            return
        except Exception as e:
            self._logger.error(str(e))
            self.warning = repr(e)
            self._valid = False
            self.validChanged.emit()
            return

        self._tx = tx

        amount = self._amount.satsInt if not self._amount.isMax else tx.output_value(
        )

        self._effectiveAmount = QEAmount(amount_sat=amount)
        self.effectiveAmountChanged.emit()

        tx_size = tx.estimated_size()
        fee = tx.get_fee()
        feerate = Decimal(fee) / tx_size  # sat/byte

        self.fee = QEAmount(amount_sat=fee)
        self.feeRate = f'{feerate:.1f}'

        #TODO
        #x_fee = run_hook('get_tx_extra_fee', self._wallet.wallet, tx)
        fee_warning_tuple = self._wallet.wallet.get_tx_fee_warning(
            invoice_amt=amount, tx_size=tx_size, fee=fee)
        if fee_warning_tuple:
            allow_send, long_warning, short_warning = fee_warning_tuple
            self.warning = long_warning
        else:
            self.warning = ''

        target, tooltip, dyn = self.config.get_fee_target()
        self.target = target

        self._valid = True
        self.validChanged.emit()

    @pyqtSlot()
    def send_onchain(self):
        if not self._valid or not self._tx:
            self._logger.debug('no valid tx')
            return

        if self.f_accept:
            self.f_accept(self._tx)
            return

        self._wallet.sign_and_broadcast(self._tx)
コード例 #16
0
ファイル: qeconfig.py プロジェクト: SomberNight/electrum
class QEConfig(AuthMixin, QObject):
    def __init__(self, config, parent=None):
        super().__init__(parent)
        self.config = config

    _logger = get_logger(__name__)

    autoConnectChanged = pyqtSignal()

    @pyqtProperty(bool, notify=autoConnectChanged)
    def autoConnect(self):
        return self.config.get('auto_connect')

    @autoConnect.setter
    def autoConnect(self, auto_connect):
        self.config.set_key('auto_connect', auto_connect, True)
        self.autoConnectChanged.emit()

    # auto_connect is actually a tri-state, expose the undefined case
    @pyqtProperty(bool, notify=autoConnectChanged)
    def autoConnectDefined(self):
        return self.config.get('auto_connect') is not None

    serverStringChanged = pyqtSignal()

    @pyqtProperty('QString', notify=serverStringChanged)
    def serverString(self):
        return self.config.get('server')

    @serverString.setter
    def serverString(self, server):
        self.config.set_key('server', server, True)
        self.serverStringChanged.emit()

    manualServerChanged = pyqtSignal()

    @pyqtProperty(bool, notify=manualServerChanged)
    def manualServer(self):
        return self.config.get('oneserver')

    @manualServer.setter
    def manualServer(self, oneserver):
        self.config.set_key('oneserver', oneserver, True)
        self.manualServerChanged.emit()

    baseUnitChanged = pyqtSignal()

    @pyqtProperty(str, notify=baseUnitChanged)
    def baseUnit(self):
        return self.config.get_base_unit()

    @baseUnit.setter
    def baseUnit(self, unit):
        self.config.set_base_unit(unit)
        self.baseUnitChanged.emit()

    thousandsSeparatorChanged = pyqtSignal()

    @pyqtProperty(bool, notify=thousandsSeparatorChanged)
    def thousandsSeparator(self):
        return self.config.get('amt_add_thousands_sep', False)

    @thousandsSeparator.setter
    def thousandsSeparator(self, checked):
        self.config.set_key('amt_add_thousands_sep', checked)
        self.config.amt_add_thousands_sep = checked
        self.thousandsSeparatorChanged.emit()

    spendUnconfirmedChanged = pyqtSignal()

    @pyqtProperty(bool, notify=spendUnconfirmedChanged)
    def spendUnconfirmed(self):
        return not self.config.get('confirmed_only', False)

    @spendUnconfirmed.setter
    def spendUnconfirmed(self, checked):
        self.config.set_key('confirmed_only', not checked, True)
        self.spendUnconfirmedChanged.emit()

    pinCodeChanged = pyqtSignal()

    @pyqtProperty(str, notify=pinCodeChanged)
    def pinCode(self):
        return self.config.get('pin_code', '')

    @pinCode.setter
    def pinCode(self, pin_code):
        if pin_code == '':
            self.pinCodeRemoveAuth()
        else:
            self.config.set_key('pin_code', pin_code, True)
            self.pinCodeChanged.emit()

    @auth_protect(method='wallet')
    def pinCodeRemoveAuth(self):
        self.config.set_key('pin_code', '', True)
        self.pinCodeChanged.emit()

    useGossipChanged = pyqtSignal()

    @pyqtProperty(bool, notify=useGossipChanged)
    def useGossip(self):
        return self.config.get('use_gossip', False)

    @useGossip.setter
    def useGossip(self, gossip):
        self.config.set_key('use_gossip', gossip)
        self.useGossipChanged.emit()

    @pyqtSlot('qint64', result=str)
    @pyqtSlot('qint64', bool, result=str)
    @pyqtSlot(QEAmount, result=str)
    @pyqtSlot(QEAmount, bool, result=str)
    def formatSats(self, satoshis, with_unit=False):
        if isinstance(satoshis, QEAmount):
            satoshis = satoshis.satsInt
        if with_unit:
            return self.config.format_amount_and_units(satoshis)
        else:
            return self.config.format_amount(satoshis)

    @pyqtSlot(QEAmount, result=str)
    @pyqtSlot(QEAmount, bool, result=str)
    def formatMilliSats(self, amount, with_unit=False):
        if isinstance(amount, QEAmount):
            msats = amount.msatsInt
        else:
            return '---'

        s = format_satoshis(msats / 1000,
                            decimal_point=self.decimal_point(),
                            precision=3)
        return s
        #if with_unit:
        #return self.config.format_amount_and_units(msats)
        #else:
        #return self.config.format_amount(satoshis)

    # TODO delegate all this to config.py/util.py
    def decimal_point(self):
        return self.config.get('decimal_point', DECIMAL_POINT_DEFAULT)

    def max_precision(self):
        return self.decimal_point() + 0  #self.extra_precision

    @pyqtSlot(str, result=QEAmount)
    def unitsToSats(self, unitAmount):
        self._amount = QEAmount()
        try:
            x = Decimal(unitAmount)
        except:
            return self._amount

        # scale it to max allowed precision, make it an int
        max_prec_amount = int(pow(10, self.max_precision()) * x)
        # if the max precision is simply what unit conversion allows, just return
        if self.max_precision() == self.decimal_point():
            self._amount = QEAmount(amount_sat=max_prec_amount)
            return self._amount
        self._logger.debug('fallthrough')
        # otherwise, scale it back to the expected unit
        #amount = Decimal(max_prec_amount) / Decimal(pow(10, self.max_precision()-self.decimal_point()))
        #return int(amount) #Decimal(amount) if not self.is_int else int(amount)
        return self._amount

    @pyqtSlot('quint64', result=float)
    def satsToUnits(self, satoshis):
        return satoshis / pow(10, self.config.decimal_point)
コード例 #17
0
ファイル: qeqr.py プロジェクト: SomberNight/electrum
class QEQRParser(QObject):
    def __init__(self, text=None, parent=None):
        super().__init__(parent)
        self._text = text
        self.qrreader = get_qr_reader()
        if not self.qrreader:
            raise Exception(
                _("The platform QR detection library is not available."))

    _logger = get_logger(__name__)

    busyChanged = pyqtSignal()
    dataChanged = pyqtSignal()
    imageChanged = pyqtSignal()

    _busy = False
    _image = None

    @pyqtSlot('QImage')
    def scanImage(self, image=None):
        if self._busy:
            self._logger.warning(
                "Already processing an image. Check 'busy' property before calling scanImage"
            )
            return

        if image == None:
            self._logger.warning("No image to decode")
            return

        self._busy = True
        self.busyChanged.emit()

        self.logImageStats(image)
        self._parseQR(image)

    def logImageStats(self, image):
        self._logger.info('width: ' + str(image.width()))
        self._logger.info('height: ' + str(image.height()))
        self._logger.info('depth: ' + str(image.depth()))
        self._logger.info('format: ' + str(image.format()))

    def _parseQR(self, image):
        self.w = image.width()
        self.h = image.height()
        img_crop_rect = self._get_crop(image, 360)
        frame_cropped = image.copy(img_crop_rect)

        async def co_parse_qr(image):
            # Convert to Y800 / GREY FourCC (single 8-bit channel)
            # This creates a copy, so we don't need to keep the frame around anymore
            frame_y800 = image.convertToFormat(QImage.Format_Grayscale8)

            self.frame_id = 0
            # Read the QR codes from the frame
            self.qrreader_res = self.qrreader.read_qr_code(
                frame_y800.constBits().__int__(), frame_y800.byteCount(),
                frame_y800.bytesPerLine(), frame_y800.width(),
                frame_y800.height(), self.frame_id)

            if len(self.qrreader_res) > 0:
                result = self.qrreader_res[0]
                self._data = result
                self.dataChanged.emit()

            self._busy = False
            self.busyChanged.emit()

        asyncio.run_coroutine_threadsafe(co_parse_qr(frame_cropped),
                                         get_asyncio_loop())

    def _get_crop(self, image: QImage, scan_size: int) -> QRect:
        """
        Returns a QRect that is scan_size x scan_size in the middle of the resolution
        """
        self.scan_pos_x = (image.width() - scan_size) // 2
        self.scan_pos_y = (image.height() - scan_size) // 2
        return QRect(self.scan_pos_x, self.scan_pos_y, scan_size, scan_size)

    @pyqtProperty(bool, notify=busyChanged)
    def busy(self):
        return self._busy

    @pyqtProperty('QImage', notify=imageChanged)
    def image(self):
        return self._image

    @pyqtProperty(str, notify=dataChanged)
    def data(self):
        return self._data.data

    @pyqtProperty('QPoint', notify=dataChanged)
    def center(self):
        (x, y) = self._data.center
        return QPoint(x + self.scan_pos_x, y + self.scan_pos_y)

    @pyqtProperty('QVariant', notify=dataChanged)
    def points(self):
        result = []
        for item in self._data.points:
            (x, y) = item
            result.append(QPoint(x + self.scan_pos_x, y + self.scan_pos_y))
        return result
コード例 #18
0
class QEAddressDetails(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)

    _logger = get_logger(__name__)

    _wallet = None
    _address = None

    _label = None
    _frozen = False
    _scriptType = None
    _status = None
    _balance = QEAmount()
    _pubkeys = None
    _privkey = None
    _derivationPath = None
    _numtx = 0

    _historyModel = None

    detailsChanged = pyqtSignal()

    walletChanged = pyqtSignal()

    @pyqtProperty(QEWallet, notify=walletChanged)
    def wallet(self):
        return self._wallet

    @wallet.setter
    def wallet(self, wallet: QEWallet):
        if self._wallet != wallet:
            self._wallet = wallet
            self.walletChanged.emit()

    addressChanged = pyqtSignal()

    @pyqtProperty(str, notify=addressChanged)
    def address(self):
        return self._address

    @address.setter
    def address(self, address: str):
        if self._address != address:
            self._logger.debug('address changed')
            self._address = address
            self.addressChanged.emit()
            self.update()

    @pyqtProperty(str, notify=detailsChanged)
    def scriptType(self):
        return self._scriptType

    @pyqtProperty(QEAmount, notify=detailsChanged)
    def balance(self):
        return self._balance

    @pyqtProperty('QStringList', notify=detailsChanged)
    def pubkeys(self):
        return self._pubkeys

    @pyqtProperty(str, notify=detailsChanged)
    def derivationPath(self):
        return self._derivationPath

    @pyqtProperty(int, notify=detailsChanged)
    def numTx(self):
        return self._numtx

    frozenChanged = pyqtSignal()

    @pyqtProperty(bool, notify=frozenChanged)
    def isFrozen(self):
        return self._frozen

    labelChanged = pyqtSignal()

    @pyqtProperty(str, notify=labelChanged)
    def label(self):
        return self._label

    @pyqtSlot(bool)
    def freeze(self, freeze: bool):
        if freeze != self._frozen:
            self._wallet.wallet.set_frozen_state_of_addresses([self._address],
                                                              freeze=freeze)
            self._frozen = freeze
            self.frozenChanged.emit()
            self._wallet.balanceChanged.emit()

    @pyqtSlot(str)
    def set_label(self, label: str):
        if label != self._label:
            self._wallet.wallet.set_label(self._address, label)
            self._label = label
            self.labelChanged.emit()

    historyModelChanged = pyqtSignal()

    @pyqtProperty(QETransactionListModel, notify=historyModelChanged)
    def historyModel(self):
        if self._historyModel is None:
            self._historyModel = QETransactionListModel(
                self._wallet.wallet,
                onchain_domain=[self._address],
                include_lightning=False)
        return self._historyModel

    def update(self):
        if self._wallet is None:
            self._logger.error('wallet undefined')
            return

        self._frozen = self._wallet.wallet.is_frozen_address(self._address)
        self.frozenChanged.emit()

        self._scriptType = self._wallet.wallet.get_txin_type(self._address)
        self._label = self._wallet.wallet.get_label(self._address)
        c, u, x = self._wallet.wallet.get_addr_balance(self._address)
        self._balance = QEAmount(amount_sat=c + u + x)
        self._pubkeys = self._wallet.wallet.get_public_keys(self._address)
        self._derivationPath = self._wallet.wallet.get_address_path_str(
            self._address)
        self._derivationPath = self._derivationPath.replace(
            'm', self._wallet.derivationPrefix)
        self._numtx = self._wallet.wallet.adb.get_address_history_len(
            self._address)
        assert (self._numtx == self.historyModel.rowCount(0))
        self.detailsChanged.emit()
コード例 #19
0
class QEChannelOpener(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)

    _logger = get_logger(__name__)

    _wallet = None
    _nodeid = None
    _amount = QEAmount()
    _valid = False
    _opentx = None

    validationError = pyqtSignal([str, str], arguments=['code', 'message'])
    conflictingBackup = pyqtSignal([str], arguments=['message'])
    channelOpenError = pyqtSignal([str], arguments=['message'])
    channelOpenSuccess = pyqtSignal([str, bool],
                                    arguments=['cid', 'has_backup'])

    dataChanged = pyqtSignal()  # generic notify signal

    walletChanged = pyqtSignal()

    @pyqtProperty(QEWallet, notify=walletChanged)
    def wallet(self):
        return self._wallet

    @wallet.setter
    def wallet(self, wallet: QEWallet):
        if self._wallet != wallet:
            self._wallet = wallet
            self.walletChanged.emit()

    nodeidChanged = pyqtSignal()

    @pyqtProperty(str, notify=nodeidChanged)
    def nodeid(self):
        return self._nodeid

    @nodeid.setter
    def nodeid(self, nodeid: str):
        if self._nodeid != nodeid:
            self._logger.debug('nodeid set -> %s' % nodeid)
            self._nodeid = nodeid
            self.nodeidChanged.emit()
            self.validate()

    amountChanged = pyqtSignal()

    @pyqtProperty(QEAmount, notify=amountChanged)
    def amount(self):
        return self._amount

    @amount.setter
    def amount(self, amount: QEAmount):
        if self._amount != amount:
            self._amount = amount
            self.amountChanged.emit()
            self.validate()

    validChanged = pyqtSignal()

    @pyqtProperty(bool, notify=validChanged)
    def valid(self):
        return self._valid

    finalizerChanged = pyqtSignal()

    @pyqtProperty(QETxFinalizer, notify=finalizerChanged)
    def finalizer(self):
        return self._finalizer

    @pyqtProperty(list, notify=dataChanged)
    def trampolineNodeNames(self):
        return list(hardcoded_trampoline_nodes().keys())

    # FIXME min channel funding amount
    # FIXME have requested funding amount
    def validate(self):
        nodeid_valid = False
        if self._nodeid:
            if not self._wallet.wallet.config.get('use_gossip', False):
                self._peer = hardcoded_trampoline_nodes()[self._nodeid]
                nodeid_valid = True
            else:
                try:
                    node_pubkey, host_port = extract_nodeid(self._nodeid)
                    host, port = host_port.split(':', 1)
                    self._peer = LNPeerAddr(host, int(port), node_pubkey)
                    nodeid_valid = True
                except ConnStringFormatError as e:
                    self.validationError.emit('invalid_nodeid', repr(e))
                except ValueError as e:
                    self.validationError.emit('invalid_nodeid', repr(e))

        if not nodeid_valid:
            self._valid = False
            self.validChanged.emit()
            return

        self._logger.debug('amount=%s' % str(self._amount))
        if not self._amount or not (self._amount.satsInt > 0
                                    or self._amount.isMax):
            self._valid = False
            self.validChanged.emit()
            return

        self._valid = True
        self.validChanged.emit()

    # FIXME "max" button in amount_dialog should enforce LN_MAX_FUNDING_SAT
    @pyqtSlot()
    @pyqtSlot(bool)
    def open_channel(self, confirm_backup_conflict=False):
        if not self.valid:
            return

        self._logger.debug('Connect String: %s' % str(self._peer))

        lnworker = self._wallet.wallet.lnworker
        if lnworker.has_conflicting_backup_with(
                self._peer.pubkey) and not confirm_backup_conflict:
            self.conflictingBackup.emit(
                messages.MGS_CONFLICTING_BACKUP_INSTANCE)
            return

        amount = '!' if self._amount.isMax else self._amount.satsInt
        self._logger.debug('amount = %s' % str(amount))

        coins = self._wallet.wallet.get_spendable_coins(None,
                                                        nonlocal_only=True)

        mktx = lambda amt: lnworker.mktx_for_open_channel(coins=coins,
                                                          funding_sat=amt,
                                                          node_id=self._peer.
                                                          pubkey,
                                                          fee_est=None)

        acpt = lambda tx: self.do_open_channel(tx, str(self._peer), None)

        self._finalizer = QETxFinalizer(self, make_tx=mktx, accept=acpt)
        self._finalizer.canRbf = False
        self._finalizer.amount = self._amount
        self._finalizer.wallet = self._wallet
        self.finalizerChanged.emit()

    def do_open_channel(self, funding_tx, conn_str, password):
        self._logger.debug('opening channel')
        # read funding_sat from tx; converts '!' to int value
        funding_sat = funding_tx.output_value_for_address(ln_dummy_address())
        lnworker = self._wallet.wallet.lnworker
        try:
            chan, funding_tx = lnworker.open_channel(connect_str=conn_str,
                                                     funding_tx=funding_tx,
                                                     funding_sat=funding_sat,
                                                     push_amt_sat=0,
                                                     password=password)
        except Exception as e:
            self._logger.exception("Problem opening channel")
            self.channelOpenError.emit(
                _('Problem opening channel: ') + '\n' + repr(e))
            return

        self._logger.debug('opening channel succeeded')
        self.channelOpenSuccess.emit(chan.channel_id.hex(),
                                     chan.has_onchain_backup())
コード例 #20
0
class QENetwork(QObject, QtEventListener):
    def __init__(self, network, parent=None):
        super().__init__(parent)
        self.network = network
        self.register_callbacks()

    _logger = get_logger(__name__)

    networkUpdated = pyqtSignal()
    blockchainUpdated = pyqtSignal()
    heightChanged = pyqtSignal([int], arguments=['height'])
    defaultServerChanged = pyqtSignal()
    proxySet = pyqtSignal()
    proxyChanged = pyqtSignal()
    statusChanged = pyqtSignal()
    feeHistogramUpdated = pyqtSignal()

    # shared signal for static properties
    dataChanged = pyqtSignal()

    _height = 0
    _status = ""

    @qt_event_listener
    def on_event_network_updated(self, *args):
        self.networkUpdated.emit()

    @qt_event_listener
    def on_event_blockchain_updated(self, *args):
        if self._height != self.network.get_local_height():
            self._height = self.network.get_local_height()
            self._logger.debug('new height: %d' % self._height)
            self.heightChanged.emit(self._height)
        self.blockchainUpdated.emit()

    @qt_event_listener
    def on_event_default_server_changed(self, *args):
        self.defaultServerChanged.emit()

    @qt_event_listener
    def on_event_proxy_set(self, *args):
        self._logger.debug('proxy set')
        self.proxySet.emit()

    @qt_event_listener
    def on_event_status(self, *args):
        self._logger.debug('status updated: %s' % self.network.connection_status)
        if self._status != self.network.connection_status:
            self._status = self.network.connection_status
            self.statusChanged.emit()

    @qt_event_listener
    def on_event_fee_histogram(self, *args):
        self._logger.debug('fee histogram updated')
        self.feeHistogramUpdated.emit()

    @pyqtProperty(int, notify=heightChanged)
    def height(self):
        return self._height

    @pyqtProperty('QString', notify=defaultServerChanged)
    def server(self):
        return str(self.network.get_parameters().server)

    @server.setter
    def server(self, server):
        net_params = self.network.get_parameters()
        try:
            server = ServerAddr.from_str_with_inference(server)
            if not server: raise Exception("failed to parse")
        except Exception:
            return
        net_params = net_params._replace(server=server)
        self.network.run_from_another_thread(self.network.set_parameters(net_params))

    @pyqtProperty('QString', notify=statusChanged)
    def status(self):
        return self._status

    @pyqtProperty(bool, notify=dataChanged)
    def isTestNet(self):
        return constants.net.TESTNET

    @pyqtProperty('QString', notify=dataChanged)
    def networkName(self):
        return constants.net.__name__.replace('Bitcoin','')

    @pyqtProperty('QVariantMap', notify=proxyChanged)
    def proxy(self):
        net_params = self.network.get_parameters()
        return net_params

    @proxy.setter
    def proxy(self, proxy_settings):
        net_params = self.network.get_parameters()
        if not proxy_settings['enabled']:
            proxy_settings = None
        net_params = net_params._replace(proxy=proxy_settings)
        self.network.run_from_another_thread(self.network.set_parameters(net_params))
        self.proxyChanged.emit()

    @pyqtProperty('QVariant', notify=feeHistogramUpdated)
    def feeHistogram(self):
        return self.network.get_status_value('fee_histogram')
コード例 #21
0
class QEAbstractInvoiceListModel(QAbstractListModel):
    _logger = get_logger(__name__)

    def __init__(self, wallet, parent=None):
        super().__init__(parent)
        self.wallet = wallet
        self.init_model()

    # define listmodel rolemap
    _ROLE_NAMES=('key', 'is_lightning', 'timestamp', 'date', 'message', 'amount',
                 'status', 'status_str', 'address', 'expiration', 'type', 'onchain_fallback',
                 'lightning_invoice')
    _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
    _ROLE_MAP  = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
    _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS))

    def rowCount(self, index):
        return len(self.invoices)

    def roleNames(self):
        return self._ROLE_MAP

    def data(self, index, role):
        invoice = self.invoices[index.row()]
        role_index = role - Qt.UserRole
        value = invoice[self._ROLE_NAMES[role_index]]

        if isinstance(value, (bool, list, int, str, QEAmount)) or value is None:
            return value
        if isinstance(value, Satoshis):
            return value.value
        return str(value)

    def clear(self):
        self.beginResetModel()
        self.invoices = []
        self.endResetModel()

    @pyqtSlot()
    def init_model(self):
        invoices = []
        for invoice in self.get_invoice_list():
            item = self.invoice_to_model(invoice)
            #self._logger.debug(str(item))
            invoices.append(item)

        self.clear()
        self.beginInsertRows(QModelIndex(), 0, len(invoices) - 1)
        self.invoices = invoices
        self.endInsertRows()

    def add_invoice(self, invoice: Invoice):
        item = self.invoice_to_model(invoice)
        self._logger.debug(str(item))

        self.beginInsertRows(QModelIndex(), 0, 0)
        self.invoices.insert(0, item)
        self.endInsertRows()

    def delete_invoice(self, key: str):
        i = 0
        for invoice in self.invoices:
            if invoice['key'] == key:
                self.beginRemoveRows(QModelIndex(), i, i)
                self.invoices.pop(i)
                self.endRemoveRows()
                break
            i = i + 1

    def get_model_invoice(self, key: str):
        for invoice in self.invoices:
            if invoice['key'] == key:
                return invoice
        return None

    @pyqtSlot(str, int)
    def updateInvoice(self, key, status):
        self._logger.debug('updating invoice for %s to %d' % (key,status))
        i = 0
        for item in self.invoices:
            if item['key'] == key:
                invoice = self.get_invoice_for_key(key)
                item['status'] = status
                item['status_str'] = invoice.get_status_str(status)
                index = self.index(i,0)
                self.dataChanged.emit(index, index, [self._ROLE_RMAP['status'], self._ROLE_RMAP['status_str']])
                return
            i = i + 1

    def invoice_to_model(self, invoice: Invoice):
        item = self.get_invoice_as_dict(invoice)
        #item['key'] = invoice.get_id()
        item['is_lightning'] = invoice.is_lightning()
        if invoice.is_lightning() and 'address' not in item:
            item['address'] = ''
        item['date'] = format_time(item['timestamp'])
        item['amount'] = QEAmount(from_invoice=invoice)
        item['onchain_fallback'] = invoice.is_lightning() and invoice._lnaddr.get_fallback_address()
        item['type'] = 'invoice'

        return item

    @abstractmethod
    def get_invoice_for_key(self, key: str):
        raise Exception('provide impl')

    @abstractmethod
    def get_invoice_list(self):
        raise Exception('provide impl')

    @abstractmethod
    def get_invoice_as_dict(self, invoice: Invoice):
        raise Exception('provide impl')
コード例 #22
0
    def __init__(self, args, config, daemon):
        super().__init__(args)

        self.logger = get_logger(__name__)

        ElectrumQmlApplication._daemon = daemon

        qmlRegisterType(QEWalletListModel, 'org.electrum', 1, 0,
                        'WalletListModel')
        qmlRegisterType(QEWallet, 'org.electrum', 1, 0, 'Wallet')
        qmlRegisterType(QEWalletDB, 'org.electrum', 1, 0, 'WalletDB')
        qmlRegisterType(QEBitcoin, 'org.electrum', 1, 0, 'Bitcoin')
        qmlRegisterType(QEQRParser, 'org.electrum', 1, 0, 'QRParser')
        qmlRegisterType(QEFX, 'org.electrum', 1, 0, 'FX')
        qmlRegisterType(QETxFinalizer, 'org.electrum', 1, 0, 'TxFinalizer')
        qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice')
        qmlRegisterType(QEInvoiceParser, 'org.electrum', 1, 0, 'InvoiceParser')
        qmlRegisterType(QEUserEnteredPayment, 'org.electrum', 1, 0,
                        'UserEnteredPayment')
        qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0,
                        'AddressDetails')
        qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails')
        qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener')
        qmlRegisterType(QELnPaymentDetails, 'org.electrum', 1, 0,
                        'LnPaymentDetails')
        qmlRegisterType(QEChannelDetails, 'org.electrum', 1, 0,
                        'ChannelDetails')
        qmlRegisterType(QESwapHelper, 'org.electrum', 1, 0, 'SwapHelper')

        qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount',
                                   'Amount can only be used as property')

        self.engine = QQmlApplicationEngine(parent=self)
        self.engine.addImportPath('./qml')

        screensize = self.primaryScreen().size()

        self.qr_ip = QEQRImageProvider(
            (7 / 8) * min(screensize.width(), screensize.height()))
        self.engine.addImageProvider('qrgen', self.qr_ip)

        # add a monospace font as we can't rely on device having one
        self.fixedFont = 'PT Mono'
        not_loaded = QFontDatabase.addApplicationFont(
            'electrum/gui/qml/fonts/PTMono-Regular.ttf') < 0
        not_loaded = QFontDatabase.addApplicationFont(
            'electrum/gui/qml/fonts/PTMono-Bold.ttf') < 0 and not_loaded
        if not_loaded:
            self.logger.warning('Could not load font PT Mono')
            self.fixedFont = 'Monospace'  # hope for the best

        self.context = self.engine.rootContext()
        self._qeconfig = QEConfig(config)
        self._qenetwork = QENetwork(daemon.network)
        self._qedaemon = QEDaemon(daemon)
        self._appController = QEAppController(self._qedaemon)
        self._maxAmount = QEAmount(is_max=True)
        self.context.setContextProperty('AppController', self._appController)
        self.context.setContextProperty('Config', self._qeconfig)
        self.context.setContextProperty('Network', self._qenetwork)
        self.context.setContextProperty('Daemon', self._qedaemon)
        self.context.setContextProperty('FixedFont', self.fixedFont)
        self.context.setContextProperty('MAX', self._maxAmount)
        self.context.setContextProperty(
            'BUILD', {
                'electrum_version': version.ELECTRUM_VERSION,
                'apk_version': version.APK_VERSION,
                'protocol_version': version.PROTOCOL_VERSION
            })

        qInstallMessageHandler(self.message_handler)

        # get notified whether root QML document loads or not
        self.engine.objectCreated.connect(self.objectCreated)
コード例 #23
0
class QELnPaymentDetails(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)

    _logger = get_logger(__name__)

    _wallet = None
    _key = None
    _date = None

    detailsChanged = pyqtSignal()

    walletChanged = pyqtSignal()

    @pyqtProperty(QEWallet, notify=walletChanged)
    def wallet(self):
        return self._wallet

    @wallet.setter
    def wallet(self, wallet: QEWallet):
        if self._wallet != wallet:
            self._wallet = wallet
            self.walletChanged.emit()

    keyChanged = pyqtSignal()

    @pyqtProperty(str, notify=keyChanged)
    def key(self):
        return self._key

    @key.setter
    def key(self, key: str):
        if self._key != key:
            self._logger.debug('key set -> %s' % key)
            self._key = key
            self.keyChanged.emit()
            self.update()

    labelChanged = pyqtSignal()

    @pyqtProperty(str, notify=labelChanged)
    def label(self):
        return self._label

    @pyqtSlot(str)
    def set_label(self, label: str):
        if label != self._label:
            self._wallet.wallet.set_label(self._key, label)
            self._label = label
            self.labelChanged.emit()

    @pyqtProperty(str, notify=detailsChanged)
    def status(self):
        return self._status

    @pyqtProperty(str, notify=detailsChanged)
    def date(self):
        return self._date

    @pyqtProperty(str, notify=detailsChanged)
    def payment_hash(self):
        return self._phash

    @pyqtProperty(str, notify=detailsChanged)
    def preimage(self):
        return self._preimage

    @pyqtProperty(str, notify=detailsChanged)
    def invoice(self):
        return self._invoice

    @pyqtProperty(QEAmount, notify=detailsChanged)
    def amount(self):
        return self._amount

    @pyqtProperty(QEAmount, notify=detailsChanged)
    def fee(self):
        return self._fee

    def update(self):
        if self._wallet is None:
            self._logger.error('wallet undefined')
            return

        if self._key not in self._wallet.wallet.lnworker.payment_info:
            self._logger.error('payment_hash not found')
            return

        # TODO this is horribly inefficient. need a payment getter/query method
        tx = self._wallet.wallet.lnworker.get_lightning_history()[bfh(
            self._key)]
        self._logger.debug(str(tx))

        self._fee = QEAmount() if not tx['fee_msat'] else QEAmount(
            amount_msat=tx['fee_msat'])
        self._amount = QEAmount(amount_msat=tx['amount_msat'])
        self._label = tx['label']
        self._date = format_time(tx['timestamp'])
        self._status = 'settled'  # TODO: other states? get_lightning_history is deciding the filter for us :(
        self._phash = tx['payment_hash']
        self._preimage = tx['preimage']

        invoice = (self._wallet.wallet.get_invoice(self._key)
                   or self._wallet.wallet.get_request(self._key))
        self._logger.debug(str(invoice))
        if invoice:
            self._invoice = invoice.lightning_invoice or ''
        else:
            self._invoice = ''

        self.detailsChanged.emit()
コード例 #24
0
class QEChannelDetails(QObject, QtEventListener):

    _logger = get_logger(__name__)
    _wallet = None
    _channelid = None
    _channel = None

    channelChanged = pyqtSignal()
    channelCloseSuccess = pyqtSignal()
    channelCloseFailed = pyqtSignal([str], arguments=['message'])

    def __init__(self, parent=None):
        super().__init__(parent)
        self.register_callbacks()
        self.destroyed.connect(lambda: self.on_destroy())

    @event_listener
    def on_event_channel(self, wallet, channel):
        if wallet == self._wallet.wallet and self._channelid == channel.channel_id.hex(
        ):
            self.channelChanged.emit()

    def on_destroy(self):
        self.unregister_callbacks()

    walletChanged = pyqtSignal()

    @pyqtProperty(QEWallet, notify=walletChanged)
    def wallet(self):
        return self._wallet

    @wallet.setter
    def wallet(self, wallet: QEWallet):
        if self._wallet != wallet:
            self._wallet = wallet
            self.walletChanged.emit()

    channelidChanged = pyqtSignal()

    @pyqtProperty(str, notify=channelidChanged)
    def channelid(self):
        return self._channelid

    @channelid.setter
    def channelid(self, channelid: str):
        if self._channelid != channelid:
            self._channelid = channelid
            if channelid:
                self.load()
            self.channelidChanged.emit()

    def load(self):
        lnchannels = self._wallet.wallet.lnworker.channels
        for channel in lnchannels.values():
            #self._logger.debug('%s == %s ?' % (self._channelid, channel.channel_id))
            if self._channelid == channel.channel_id.hex():
                self._channel = channel
                self.channelChanged.emit()

    @pyqtProperty(str, notify=channelChanged)
    def name(self):
        if not self._channel:
            return
        return self._wallet.wallet.lnworker.get_node_alias(
            self._channel.node_id) or self._channel.node_id.hex()

    @pyqtProperty(str, notify=channelChanged)
    def pubkey(self):
        return self._channel.node_id.hex()  #if self._channel else ''

    @pyqtProperty(str, notify=channelChanged)
    def short_cid(self):
        return self._channel.short_id_for_GUI()

    @pyqtProperty(str, notify=channelChanged)
    def state(self):
        return self._channel.get_state_for_GUI()

    @pyqtProperty(str, notify=channelChanged)
    def initiator(self):
        return 'Local' if self._channel.constraints.is_initiator else 'Remote'

    @pyqtProperty(QEAmount, notify=channelChanged)
    def capacity(self):
        self._capacity = QEAmount(amount_sat=self._channel.get_capacity())
        return self._capacity

    @pyqtProperty(QEAmount, notify=channelChanged)
    def canSend(self):
        self._can_send = QEAmount(
            amount_sat=self._channel.available_to_spend(LOCAL) / 1000)
        return self._can_send

    @pyqtProperty(QEAmount, notify=channelChanged)
    def canReceive(self):
        self._can_receive = QEAmount(
            amount_sat=self._channel.available_to_spend(REMOTE) / 1000)
        return self._can_receive

    @pyqtProperty(bool, notify=channelChanged)
    def frozenForSending(self):
        return self._channel.is_frozen_for_sending()

    @pyqtProperty(bool, notify=channelChanged)
    def frozenForReceiving(self):
        return self._channel.is_frozen_for_receiving()

    @pyqtProperty(str, notify=channelChanged)
    def channelType(self):
        return self._channel.storage['channel_type'].name_minimal

    @pyqtProperty(bool, notify=channelChanged)
    def isOpen(self):
        return self._channel.is_open()

    @pyqtProperty(bool, notify=channelChanged)
    def canClose(self):
        return self.canCoopClose or self.canForceClose

    @pyqtProperty(bool, notify=channelChanged)
    def canCoopClose(self):
        return ChanCloseOption.COOP_CLOSE in self._channel.get_close_options()

    @pyqtProperty(bool, notify=channelChanged)
    def canForceClose(self):
        return ChanCloseOption.LOCAL_FCLOSE in self._channel.get_close_options(
        )

    @pyqtProperty(bool, notify=channelChanged)
    def canDelete(self):
        return self._channel.can_be_deleted()

    @pyqtProperty(str, notify=channelChanged)
    def message_force_close(self, notify=channelChanged):
        return _(messages.MSG_REQUEST_FORCE_CLOSE)

    @pyqtSlot()
    def freezeForSending(self):
        lnworker = self._channel.lnworker
        if lnworker.channel_db or lnworker.is_trampoline_peer(
                self._channel.node_id):
            self._channel.set_frozen_for_sending(not self.frozenForSending)
            self.channelChanged.emit()
        else:
            self._logger.debug(
                messages.MSG_NON_TRAMPOLINE_CHANNEL_FROZEN_WITHOUT_GOSSIP)

    @pyqtSlot()
    def freezeForReceiving(self):
        lnworker = self._channel.lnworker
        if lnworker.channel_db or lnworker.is_trampoline_peer(
                self._channel.node_id):
            self._channel.set_frozen_for_receiving(not self.frozenForReceiving)
            self.channelChanged.emit()
        else:
            self._logger.debug(
                messages.MSG_NON_TRAMPOLINE_CHANNEL_FROZEN_WITHOUT_GOSSIP)

    # this method assumes the qobject is not destroyed before the close either fails or succeeds
    @pyqtSlot(str)
    def close_channel(self, closetype):
        async def do_close(closetype, channel_id):
            try:
                if closetype == 'remote_force':
                    await self._wallet.wallet.lnworker.request_force_close(
                        channel_id)
                elif closetype == 'local_force':
                    await self._wallet.wallet.lnworker.force_close_channel(
                        channel_id)
                else:
                    await self._wallet.wallet.lnworker.close_channel(channel_id
                                                                     )
                self.channelCloseSuccess.emit()
            except Exception as e:
                self._logger.exception("Could not close channel: " + repr(e))
                self.channelCloseFailed.emit(
                    _('Could not close channel: ') + repr(e))

        loop = self._wallet.wallet.network.asyncio_loop
        coro = do_close(closetype, self._channel.channel_id)
        asyncio.run_coroutine_threadsafe(coro, loop)

    @pyqtSlot()
    def deleteChannel(self):
        self._wallet.wallet.lnworker.remove_channel(self._channel.channel_id)
コード例 #25
0
class QEWallet(AuthMixin, QObject, QtEventListener):
    __instances = []

    # this factory method should be used to instantiate QEWallet
    # so we have only one QEWallet for each electrum.wallet
    @classmethod
    def getInstanceFor(cls, wallet):
        for i in cls.__instances:
            if i.wallet == wallet:
                return i
        i = QEWallet(wallet)
        cls.__instances.append(i)
        return i

    _logger = get_logger(__name__)

    # emitted when wallet wants to display a user notification
    # actual presentation should be handled on app or window level
    userNotify = pyqtSignal(object, object)

    # shared signal for many static wallet properties
    dataChanged = pyqtSignal()

    isUptodateChanged = pyqtSignal()
    requestStatusChanged = pyqtSignal([str, int], arguments=['key', 'status'])
    requestCreateSuccess = pyqtSignal()
    requestCreateError = pyqtSignal([str, str], arguments=['code', 'error'])
    invoiceStatusChanged = pyqtSignal([str, int], arguments=['key', 'status'])
    invoiceCreateSuccess = pyqtSignal()
    invoiceCreateError = pyqtSignal([str, str], arguments=['code', 'error'])
    paymentSucceeded = pyqtSignal([str], arguments=['key'])
    paymentFailed = pyqtSignal([str, str], arguments=['key', 'reason'])
    requestNewPassword = pyqtSignal()

    _network_signal = pyqtSignal(str, object)

    def __init__(self, wallet, parent=None):
        super().__init__(parent)
        self.wallet = wallet

        self._historyModel = None
        self._addressModel = None
        self._requestModel = None
        self._invoiceModel = None
        self._channelModel = None

        self.tx_notification_queue = queue.Queue()
        self.tx_notification_last_time = 0

        self.notification_timer = QTimer(self)
        self.notification_timer.setSingleShot(False)
        self.notification_timer.setInterval(500)  # msec
        self.notification_timer.timeout.connect(self.notify_transactions)

        # To avoid leaking references to "self" that prevent the
        # window from being GC-ed when closed, callbacks should be
        # methods of this class only, and specifically not be
        # partials, lambdas or methods of subobjects.  Hence...

        self.register_callbacks()
        self.destroyed.connect(lambda: self.on_destroy())

    @pyqtProperty(bool, notify=isUptodateChanged)
    def isUptodate(self):
        return self.wallet.is_up_to_date()

    @event_listener
    def on_event_status(self, *args, **kwargs):
        self.isUptodateChanged.emit()

    @event_listener
    def on_event_request_status(self, wallet, key, status):
        if wallet == self.wallet:
            self._logger.debug('request status %d for key %s' % (status, key))
            self.requestStatusChanged.emit(key, status)

    @event_listener
    def on_event_invoice_status(self, wallet, key):
        if wallet == self.wallet:
            self._logger.debug('invoice status update for key %s' % key)
            # FIXME event doesn't pass the new status, so we need to retrieve
            invoice = self.wallet.get_invoice(key)
            if invoice:
                status = self.wallet.get_invoice_status(invoice)
                self.invoiceStatusChanged.emit(key, status)
            else:
                self._logger.debug(f'No invoice found for key {key}')

    @qt_event_listener
    def on_event_new_transaction(self, *args):
        wallet, tx = args
        if wallet == self.wallet:
            self.add_tx_notification(tx)
            self.historyModel.init_model()  # TODO: be less dramatic

    @qt_event_listener
    def on_event_verified(self, wallet, txid, info):
        if wallet == self.wallet:
            self.historyModel.update_tx(txid, info)

    @event_listener
    def on_event_wallet_updated(self, wallet):
        if wallet == self.wallet:
            self._logger.debug('wallet %s updated' % str(wallet))
            self.balanceChanged.emit()

    @event_listener
    def on_event_channel(self, wallet, channel):
        if wallet == self.wallet:
            self.balanceChanged.emit()

    @event_listener
    def on_event_channels_updated(self, wallet):
        if wallet == self.wallet:
            self.balanceChanged.emit()

    @qt_event_listener
    def on_event_payment_succeeded(self, wallet, key):
        if wallet == self.wallet:
            self.paymentSucceeded.emit(key)
            self.historyModel.init_model()  # TODO: be less dramatic

    @event_listener
    def on_event_payment_failed(self, wallet, key, reason):
        if wallet == self.wallet:
            self.paymentFailed.emit(key, reason)

    def on_destroy(self):
        self.unregister_callbacks()

    def add_tx_notification(self, tx):
        self._logger.debug('new transaction event')
        self.tx_notification_queue.put(tx)
        if not self.notification_timer.isActive():
            self._logger.debug('starting wallet notification timer')
            self.notification_timer.start()

    def notify_transactions(self):
        if self.tx_notification_queue.qsize() == 0:
            self._logger.debug(
                'queue empty, stopping wallet notification timer')
            self.notification_timer.stop()
            return
        if not self.wallet.is_up_to_date():
            return  # no notifications while syncing
        now = time.time()
        rate_limit = 20  # seconds
        if self.tx_notification_last_time + rate_limit > now:
            return
        self.tx_notification_last_time = now
        self._logger.info("Notifying app about new transactions")
        txns = []
        while True:
            try:
                txns.append(self.tx_notification_queue.get_nowait())
            except queue.Empty:
                break

        config = self.wallet.config
        # Combine the transactions if there are at least three
        if len(txns) >= 3:
            total_amount = 0
            for tx in txns:
                tx_wallet_delta = self.wallet.get_wallet_delta(tx)
                if not tx_wallet_delta.is_relevant:
                    continue
                total_amount += tx_wallet_delta.delta
            self.userNotify.emit(
                self.wallet,
                _("{} new transactions: Total amount received in the new transactions {}"
                  ).format(len(txns),
                           config.format_amount_and_units(total_amount)))
        else:
            for tx in txns:
                tx_wallet_delta = self.wallet.get_wallet_delta(tx)
                if not tx_wallet_delta.is_relevant:
                    continue
                self.userNotify.emit(
                    self.wallet,
                    _("New transaction: {}").format(
                        config.format_amount_and_units(tx_wallet_delta.delta)))

    historyModelChanged = pyqtSignal()

    @pyqtProperty(QETransactionListModel, notify=historyModelChanged)
    def historyModel(self):
        if self._historyModel is None:
            self._historyModel = QETransactionListModel(self.wallet)
        return self._historyModel

    addressModelChanged = pyqtSignal()

    @pyqtProperty(QEAddressListModel, notify=addressModelChanged)
    def addressModel(self):
        if self._addressModel is None:
            self._addressModel = QEAddressListModel(self.wallet)
        return self._addressModel

    requestModelChanged = pyqtSignal()

    @pyqtProperty(QERequestListModel, notify=requestModelChanged)
    def requestModel(self):
        if self._requestModel is None:
            self._requestModel = QERequestListModel(self.wallet)
        return self._requestModel

    invoiceModelChanged = pyqtSignal()

    @pyqtProperty(QEInvoiceListModel, notify=invoiceModelChanged)
    def invoiceModel(self):
        if self._invoiceModel is None:
            self._invoiceModel = QEInvoiceListModel(self.wallet)
        return self._invoiceModel

    channelModelChanged = pyqtSignal()

    @pyqtProperty(QEChannelListModel, notify=channelModelChanged)
    def channelModel(self):
        if self._channelModel is None:
            self._channelModel = QEChannelListModel(self.wallet)
        return self._channelModel

    nameChanged = pyqtSignal()

    @pyqtProperty(str, notify=nameChanged)
    def name(self):
        return self.wallet.basename()

    isLightningChanged = pyqtSignal()

    @pyqtProperty(bool, notify=isLightningChanged)
    def isLightning(self):
        return bool(self.wallet.lnworker)

    @pyqtProperty(bool, notify=dataChanged)
    def canHaveLightning(self):
        return self.wallet.can_have_lightning()

    @pyqtProperty(bool, notify=dataChanged)
    def hasSeed(self):
        return self.wallet.has_seed()

    @pyqtProperty(str, 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(str, notify=dataChanged)
    def derivationPrefix(self):
        keystores = self.wallet.get_keystores()
        if len(keystores) > 1:
            self._logger.debug('multiple keystores not supported yet')
        return keystores[0].get_derivation_prefix()

    @pyqtProperty(str, notify=dataChanged)
    def masterPubkey(self):
        return self.wallet.get_master_public_key()

    balanceChanged = pyqtSignal()

    @pyqtProperty(QEAmount, notify=balanceChanged)
    def frozenBalance(self):
        c, u, x = self.wallet.get_frozen_balance()
        self._frozenbalance = QEAmount(amount_sat=c + x)
        return self._frozenbalance

    @pyqtProperty(QEAmount, notify=balanceChanged)
    def unconfirmedBalance(self):
        self._unconfirmedbalance = QEAmount(
            amount_sat=self.wallet.get_balance()[1])
        return self._unconfirmedbalance

    @pyqtProperty(QEAmount, notify=balanceChanged)
    def confirmedBalance(self):
        c, u, x = self.wallet.get_balance()
        self._confirmedbalance = QEAmount(amount_sat=c + x)
        return self._confirmedbalance

    @pyqtProperty(QEAmount, notify=balanceChanged)
    def lightningBalance(self):
        if not self.isLightning:
            self._lightningbalance = QEAmount()
        else:
            self._lightningbalance = QEAmount(
                amount_sat=int(self.wallet.lnworker.get_balance()))
        return self._lightningbalance

    @pyqtProperty(QEAmount, notify=balanceChanged)
    def lightningCanSend(self):
        if not self.isLightning:
            self._lightningcansend = QEAmount()
        else:
            self._lightningcansend = QEAmount(
                amount_sat=int(self.wallet.lnworker.num_sats_can_send()))
        return self._lightningcansend

    @pyqtProperty(QEAmount, notify=balanceChanged)
    def lightningCanReceive(self):
        if not self.isLightning:
            self._lightningcanreceive = QEAmount()
        else:
            self._lightningcanreceive = QEAmount(
                amount_sat=int(self.wallet.lnworker.num_sats_can_receive()))
        return self._lightningcanreceive

    @pyqtSlot()
    def enableLightning(self):
        self.wallet.init_lightning(
            password=None)  # TODO pass password if needed
        self.isLightningChanged.emit()

    @pyqtSlot(str, int, int, bool)
    def send_onchain(self, address, amount, fee=None, rbf=False):
        self._logger.info('send_onchain: %s %d' % (address, 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)]
        self._logger.info(str(outputs))
        output_values = [x.value for x in outputs]
        if any(parse_max_spend(outval) for outval in output_values):
            output_value = '!'
        else:
            output_value = sum(output_values)
        self._logger.info(str(output_value))
        # see qt/confirm_tx_dialog qt/main_window
        tx = self.wallet.make_unsigned_transaction(coins=coins,
                                                   outputs=outputs,
                                                   fee=None)
        self._logger.info(str(tx.to_json()))

        use_rbf = bool(self.wallet.config.get('use_rbf', True))
        tx.set_rbf(use_rbf)
        self.sign_and_broadcast(tx)

    @auth_protect
    def sign_and_broadcast(self, tx):
        def cb(result):
            self._logger.info('signing was succesful? %s' % str(result))

        tx = self.wallet.sign_transaction(tx, None)
        if not tx.is_complete():
            self._logger.info('tx not complete')
            return

        self.network = self.wallet.network  # TODO not always defined?

        try:
            self._logger.info('running broadcast in thread')
            self.network.run_from_another_thread(
                self.network.broadcast_transaction(tx))
            self._logger.info('broadcast submit done')
        except TxBroadcastError as e:
            self._logger.info(e)
            return
        except BestEffortRequestFailed as e:
            self._logger.info(e)
            return

        return

    paymentAuthRejected = pyqtSignal()

    def ln_auth_rejected(self):
        self.paymentAuthRejected.emit()

    @pyqtSlot(str)
    @auth_protect(reject='ln_auth_rejected')
    def pay_lightning_invoice(self, invoice_key):
        self._logger.debug('about to pay LN')
        invoice = self.wallet.get_invoice(invoice_key)
        assert (invoice)
        assert (invoice.lightning_invoice)

        amount_msat = invoice.get_amount_msat()

        def pay_thread():
            try:
                coro = self.wallet.lnworker.pay_invoice(
                    invoice.lightning_invoice, amount_msat=amount_msat)
                fut = asyncio.run_coroutine_threadsafe(
                    coro, self.wallet.network.asyncio_loop)
                fut.result()
            except Exception as e:
                self.userNotify.emit(repr(e))

        threading.Thread(target=pay_thread).start()

    def create_bitcoin_request(self, amount: int, message: str,
                               expiration: int,
                               ignore_gap: bool) -> Optional[str]:
        addr = self.wallet.get_unused_address()
        if addr is None:
            if not self.wallet.is_deterministic():  # imported wallet
                # TODO implement
                return
                #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 ignore_gap:
                    self.requestCreateError.emit(
                        'gaplimit',
                        _("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_key = self.wallet.create_request(amount, message, expiration, addr)
        #try:
        #self.wallet.add_payment_request(req)
        #except Exception as e:
        #self.logger.exception('Error adding payment request')
        #self.requestCreateError.emit('fatal',_('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)

        return req_key, addr

    @pyqtSlot(QEAmount, str, int)
    @pyqtSlot(QEAmount, str, int, bool)
    @pyqtSlot(QEAmount, str, int, bool, bool)
    def create_request(self,
                       amount: QEAmount,
                       message: str,
                       expiration: int,
                       is_lightning: bool = False,
                       ignore_gap: bool = False):
        # TODO: unify this method and create_bitcoin_request
        try:
            if is_lightning:
                if not self.wallet.lnworker.channels:
                    self.requestCreateError.emit(
                        'fatal',
                        _("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)
                # TODO fallback address robustness
                addr = self.wallet.get_unused_address()
                key = self.wallet.create_request(amount.satsInt, message,
                                                 expiration, addr)
            else:
                key, addr = self.create_bitcoin_request(
                    amount.satsInt, message, expiration, ignore_gap)
                if not key:
                    return
                self.addressModel.init_model()
        except InvoiceError as e:
            self.requestCreateError.emit(
                'fatal',
                _('Error creating payment request') + ':\n' + str(e))
            return

        assert key is not None
        self._requestModel.add_invoice(self.wallet.get_request(key))
        self.requestCreateSuccess.emit()

    @pyqtSlot(str)
    def delete_request(self, key: str):
        self._logger.debug('delete req %s' % key)
        self.wallet.delete_request(key)
        self._requestModel.delete_invoice(key)

    @pyqtSlot(str, result='QVariant')
    def get_request(self, key: str):
        return self._requestModel.get_model_invoice(key)

    @pyqtSlot(str)
    def delete_invoice(self, key: str):
        self._logger.debug('delete inv %s' % key)
        self.wallet.delete_invoice(key)
        self._invoiceModel.delete_invoice(key)

    @pyqtSlot(str, result='QVariant')
    def get_invoice(self, key: str):
        return self._invoiceModel.get_model_invoice(key)

    @pyqtSlot(str, result=bool)
    def verify_password(self, password):
        try:
            self.wallet.storage.check_password(password)
            return True
        except InvalidPassword as e:
            return False

    @pyqtSlot(str)
    def set_password(self, password):
        storage = self.wallet.storage

        # HW wallet not supported yet
        if storage.is_encrypted_with_hw_device():
            return

        try:
            self.wallet.update_password(self.password,
                                        password,
                                        encrypt_storage=True)
            self.password = password
        except InvalidPassword as e:
            self._logger.exception(repr(e))
コード例 #26
0
class QEChannelListModel(QAbstractListModel, QtEventListener):
    _logger = get_logger(__name__)

    # define listmodel rolemap
    _ROLE_NAMES = ('cid', 'state', 'initiator', 'capacity', 'can_send',
                   'can_receive', 'l_csv_delay', 'r_csv_delay', 'send_frozen',
                   'receive_frozen', 'type', 'node_id', 'node_alias',
                   'short_cid', 'funding_tx')
    _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
    _ROLE_MAP = dict(
        zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
    _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS))

    _network_signal = pyqtSignal(str, object)

    def __init__(self, wallet, parent=None):
        super().__init__(parent)
        self.wallet = wallet
        self.init_model()

        # To avoid leaking references to "self" that prevent the
        # window from being GC-ed when closed, callbacks should be
        # methods of this class only, and specifically not be
        # partials, lambdas or methods of subobjects.  Hence...
        self.register_callbacks()
        self.destroyed.connect(lambda: self.on_destroy())

    @qt_event_listener
    def on_event_channel(self, wallet, channel):
        if wallet == self.wallet:
            self.on_channel_updated(channel)

    #    elif event == 'channels_updated':
    @qt_event_listener
    def on_event_channels_updated(self, wallet):
        if wallet == self.wallet:
            self.init_model()  # TODO: remove/add less crude than full re-init

    def on_destroy(self):
        self.unregister_callbacks()

    def rowCount(self, index):
        return len(self.channels)

    def roleNames(self):
        return self._ROLE_MAP

    def data(self, index, role):
        tx = self.channels[index.row()]
        role_index = role - Qt.UserRole
        value = tx[self._ROLE_NAMES[role_index]]
        if isinstance(value,
                      (bool, list, int, str, QEAmount)) or value is None:
            return value
        if isinstance(value, Satoshis):
            return value.value
        return str(value)

    def clear(self):
        self.beginResetModel()
        self.channels = []
        self.endResetModel()

    def channel_to_model(self, lnc):
        lnworker = self.wallet.lnworker
        item = {}
        item['cid'] = lnc.channel_id.hex()
        item['node_alias'] = lnworker.get_node_alias(
            lnc.node_id) or lnc.node_id.hex()
        item['short_cid'] = lnc.short_id_for_GUI()
        item['state'] = lnc.get_state_for_GUI()
        item['state_code'] = lnc.get_state()
        item['capacity'] = QEAmount(amount_sat=lnc.get_capacity())
        item['can_send'] = QEAmount(amount_msat=lnc.available_to_spend(LOCAL))
        item['can_receive'] = QEAmount(
            amount_msat=lnc.available_to_spend(REMOTE))
        return item

    numOpenChannelsChanged = pyqtSignal()

    @pyqtProperty(int, notify=numOpenChannelsChanged)
    def numOpenChannels(self):
        return sum([
            1 if x['state_code'] == ChannelState.OPEN else 0
            for x in self.channels
        ])

    @pyqtSlot()
    def init_model(self):
        self._logger.debug('init_model')
        if not self.wallet.lnworker:
            self._logger.warning('lnworker should be defined')
            return

        channels = []

        lnchannels = self.wallet.lnworker.channels
        for channel in lnchannels.values():
            item = self.channel_to_model(channel)
            channels.append(item)

        self.clear()
        self.beginInsertRows(QModelIndex(), 0, len(channels) - 1)
        self.channels = channels
        self.endInsertRows()

    def on_channel_updated(self, channel):
        i = 0
        for c in self.channels:
            if c['cid'] == channel.channel_id.hex():
                self.do_update(i, channel)
                break
            i = i + 1

    def do_update(self, modelindex, channel):
        modelitem = self.channels[modelindex]
        modelitem.update(self.channel_to_model(channel))

        mi = self.createIndex(modelindex, 0)
        self.dataChanged.emit(mi, mi, self._ROLE_KEYS)
        self.numOpenChannelsChanged.emit()

    @pyqtSlot(str)
    def new_channel(self, cid):
        self._logger.debug('new channel with cid %s' % cid)
        lnchannels = self.wallet.lnworker.channels
        for channel in lnchannels.values():
            self._logger.debug(repr(channel))
            if cid == channel.channel_id.hex():
                item = self.channel_to_model(channel)
                self._logger.debug(item)
                self.beginInsertRows(QModelIndex(), 0, 0)
                self.channels.insert(0, item)
                self.endInsertRows()

    @pyqtSlot(str)
    def remove_channel(self, cid):
        self._logger.debug('remove channel with cid %s' % cid)
        i = 0
        for channel in self.channels:
            if cid == channel['cid']:
                self._logger.debug(cid)
                self.beginRemoveRows(QModelIndex(), i, i)
                self.channels.remove(channel)
                self.endRemoveRows()
                return
            i = i + 1
コード例 #27
0
import re

from electrum import constants
from electrum.plugin import BasePlugin, hook
from electrum.i18n import _
from electrum.util import UserFacingException
from electrum.logging import get_logger
from electrum.network import Network

# Introduced in Electrum v4
try:
    from electrum.interface import ServerAddr
except:
    pass

_logger = get_logger('plugins.bwt')

plugin_dir = os.path.dirname(__file__)

bwt_bin = os.path.join(plugin_dir, 'bwt')
if platform.system() == 'Windows':
    bwt_bin = '%s.exe' % bwt_bin


class BwtPlugin(BasePlugin):
    wallets = set()
    proc = None
    prev_settings = None

    def __init__(self, parent, config, name):
        BasePlugin.__init__(self, parent, config, name)
コード例 #28
0
class QEBitcoin(QObject):
    def __init__(self, config, parent=None):
        super().__init__(parent)
        self.config = config

    _logger = get_logger(__name__)

    generatedSeedChanged = pyqtSignal()
    generatedSeed = ''

    seedValidChanged = pyqtSignal()
    seedValid = False

    seedTypeChanged = pyqtSignal()
    seedType = ''

    validationMessageChanged = pyqtSignal()
    validationMessage = ''

    @pyqtProperty('QString', notify=generatedSeedChanged)
    def generated_seed(self):
        return self.generatedSeed

    @pyqtProperty(bool, notify=seedValidChanged)
    def seed_valid(self):
        return self.seedValid

    @pyqtProperty('QString', notify=seedTypeChanged)
    def seed_type(self):
        return self.seedType

    @pyqtProperty('QString', notify=validationMessageChanged)
    def validation_message(self):
        return self.validationMessage

    @pyqtSlot()
    @pyqtSlot(str)
    @pyqtSlot(str, str)
    def generate_seed(self, seed_type='segwit', language='en'):
        self._logger.debug('generating seed of type ' + str(seed_type))

        async def co_gen_seed(seed_type, language):
            self.generatedSeed = mnemonic.Mnemonic(language).make_seed(
                seed_type=seed_type)
            self._logger.debug('seed generated')
            self.generatedSeedChanged.emit()

        asyncio.run_coroutine_threadsafe(co_gen_seed(seed_type, language),
                                         get_asyncio_loop())

    @pyqtSlot(str)
    @pyqtSlot(str, bool, bool)
    @pyqtSlot(str, bool, bool, str, str, str)
    def verify_seed(self,
                    seed,
                    bip39=False,
                    slip39=False,
                    wallet_type='standard',
                    language='en'):
        self._logger.debug('bip39 ' + str(bip39))
        self._logger.debug('slip39 ' + str(slip39))

        seed_type = ''
        seed_valid = False
        validation_message = ''

        if not (bip39 or slip39):
            seed_type = mnemonic.seed_type(seed)
            if seed_type != '':
                seed_valid = True
        elif bip39:
            is_checksum, is_wordlist = bip39_is_checksum_valid(seed)
            status = ('checksum: ' + ('ok' if is_checksum else 'failed')
                      ) if is_wordlist else 'unknown wordlist'
            validation_message = 'BIP39 (%s)' % status

            if is_checksum:
                seed_type = 'bip39'
                seed_valid = True

        elif slip39:  # TODO: incomplete impl, this code only validates a single share.
            try:
                share = decode_mnemonic(seed)
                seed_type = 'slip39'
                validation_message = 'SLIP39: share #%d in %dof%d scheme' % (
                    share.group_index, share.group_threshold,
                    share.group_count)
            except Slip39Error as e:
                validation_message = 'SLIP39: %s' % str(e)
            seed_valid = False  # for now

        # cosigning seed
        if wallet_type != 'standard' and seed_type not in [
                'standard', 'segwit'
        ]:
            seed_type = ''
            seed_valid = False

        self.seedType = seed_type
        self.seedTypeChanged.emit()

        if self.validationMessage != validation_message:
            self.validationMessage = validation_message
            self.validationMessageChanged.emit()

        if self.seedValid != seed_valid:
            self.seedValid = seed_valid
            self.seedValidChanged.emit()

        self._logger.debug('seed verified: ' + str(seed_valid))

    @pyqtSlot(str, result=bool)
    def verify_derivation_path(self, path):
        return is_bip32_derivation(path)

    @pyqtSlot(str, result='QVariantMap')
    def parse_uri(self, uri: str) -> dict:
        try:
            return parse_URI(uri)
        except InvalidBitcoinURI as e:
            return {'error': str(e)}

    @pyqtSlot(str, QEAmount, str, int, int, result=str)
    def create_bip21_uri(self, address, satoshis, message, timestamp, expiry):
        extra_params = {}
        if expiry:
            extra_params['time'] = str(timestamp)
            extra_params['exp'] = str(expiry)

        return create_bip21_uri(address,
                                satoshis.satsInt,
                                message,
                                extra_query_params=extra_params)
コード例 #29
0
ファイル: network_dialog.py プロジェクト: vanife/electrum
from PyQt5.QtGui import QFontMetrics

from electrum.i18n import _
from electrum import constants, blockchain, util
from electrum.interface import ServerAddr, PREFERRED_NETWORK_PROTOCOL
from electrum.network import Network
from electrum.logging import get_logger

from .util import (Buttons, CloseButton, HelpButton, read_QIcon, char_width_in_lineedit,
                   PasswordLineEdit)

if TYPE_CHECKING:
    from electrum.simple_config import SimpleConfig


_logger = get_logger(__name__)

protocol_names = ['TCP', 'SSL']
protocol_letters = 'ts'

class NetworkDialog(QDialog):
    def __init__(self, network, config, network_updated_signal_obj):
        QDialog.__init__(self)
        self.setWindowTitle(_('Network'))
        self.setMinimumSize(500, 500)
        self.nlayout = NetworkChoiceLayout(network, config)
        self.network_updated_signal_obj = network_updated_signal_obj
        vbox = QVBoxLayout(self)
        vbox.addLayout(self.nlayout.layout())
        vbox.addLayout(Buttons(CloseButton(self)))
        self.network_updated_signal_obj.network_updated_signal.connect(
コード例 #30
0
class QEWalletDB(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)

        from .qeapp import ElectrumQmlApplication
        self.daemon = ElectrumQmlApplication._daemon

        self.reset()

    _logger = get_logger(__name__)

    fileNotFound = pyqtSignal()
    pathChanged = pyqtSignal([bool], arguments=["ready"])
    needsPasswordChanged = pyqtSignal()
    needsHWDeviceChanged = pyqtSignal()
    passwordChanged = pyqtSignal()
    validPasswordChanged = pyqtSignal()
    requiresSplitChanged = pyqtSignal()
    splitFinished = pyqtSignal()
    readyChanged = pyqtSignal()
    createError = pyqtSignal([str], arguments=["error"])
    createSuccess = pyqtSignal()
    invalidPassword = pyqtSignal()

    def reset(self):
        self._path = None
        self._needsPassword = False
        self._needsHWDevice = False
        self._password = ''
        self._requiresSplit = False
        self._validPassword = True

        self._storage = None
        self._db = None

        self._ready = False

    @pyqtProperty('QString', notify=pathChanged)
    def path(self):
        return self._path

    @path.setter
    def path(self, wallet_path):
        if wallet_path == self._path:
            return

        self._logger.info('setting path: ' + wallet_path)
        self.reset()
        self._path = wallet_path

        self.pathChanged.emit(self._ready)

    @pyqtProperty(bool, notify=needsPasswordChanged)
    def needsPassword(self):
        return self._needsPassword

    @needsPassword.setter
    def needsPassword(self, wallet_needs_password):
        if wallet_needs_password == self._needsPassword:
            return

        self._needsPassword = wallet_needs_password
        self.needsPasswordChanged.emit()

    @pyqtProperty(bool, notify=needsHWDeviceChanged)
    def needsHWDevice(self):
        return self._needsHWDevice

    @needsHWDevice.setter
    def needsHWDevice(self, wallet_needs_hw_device):
        if wallet_needs_hw_device == self._needsHWDevice:
            return

        self._needsHWDevice = wallet_needs_hw_device
        self.needsHWDeviceChanged.emit()

    @pyqtProperty('QString', notify=passwordChanged)
    def password(self):
        return ''  # no read access

    @password.setter
    def password(self, wallet_password):
        if wallet_password == self._password:
            return

        self._password = wallet_password
        self.passwordChanged.emit()

    @pyqtProperty(bool, notify=requiresSplitChanged)
    def requiresSplit(self):
        return self._requiresSplit

    @pyqtProperty(bool, notify=validPasswordChanged)
    def validPassword(self):
        return self._validPassword

    @validPassword.setter
    def validPassword(self, validPassword):
        if self._validPassword != validPassword:
            self._validPassword = validPassword
            self.validPasswordChanged.emit()

    @pyqtProperty(bool, notify=readyChanged)
    def ready(self):
        return self._ready

    @pyqtSlot()
    def verify(self):
        self.load_storage()
        if self._storage:
            self.load_db()

    @pyqtSlot()
    def doSplit(self):
        self._logger.warning('doSplit')
        if not self._requiresSplit:
            return

        self._db.split_accounts(self._path)

        self.splitFinished.emit()

    def load_storage(self):
        self._storage = WalletStorage(self._path)
        if not self._storage.file_exists():
            self._logger.warning('file does not exist')
            self.fileNotFound.emit()
            self._storage = None
            return

        if self._storage.is_encrypted():
            self.needsPassword = True

            try:
                self._storage.decrypt(
                    '' if not self._password else self._password)
                self.validPassword = True
            except InvalidPassword as e:
                self.validPassword = False
                self.invalidPassword.emit()

        if not self._storage.is_past_initial_decryption():
            self._storage = None

    def load_db(self):
        # needs storage accessible
        self._db = WalletDB(self._storage.read(), manual_upgrades=True)
        if self._db.requires_split():
            self._logger.warning('wallet requires split')
            self._requiresSplit = True
            self.requiresSplitChanged.emit()
            return
        if self._db.get_action():
            self._logger.warning(
                'action pending. QML version doesn\'t support continuation of wizard'
            )
            return

        if self._db.requires_upgrade():
            self._logger.warning('wallet requires upgrade, upgrading')
            self._db.upgrade()
            self._db.write(self._storage)

        self._ready = True
        self.readyChanged.emit()

    @pyqtSlot('QJSValue', bool, str)
    def create_storage(self, js_data, single_password_enabled,
                       single_password):
        self._logger.info('Creating wallet from wizard data')
        data = js_data.toVariant()
        self._logger.debug(str(data))

        if single_password_enabled and single_password:
            data['encrypt'] = True
            data['password'] = single_password

        try:
            path = os.path.join(
                os.path.dirname(self.daemon.config.get_wallet_path()),
                data['wallet_name'])
            if os.path.exists(path):
                raise Exception('file already exists at path')
            storage = WalletStorage(path)

            if data['seed_type'] in ['old', 'standard',
                                     'segwit']:  #2fa, 2fa-segwit
                self._logger.debug('creating keystore from electrum seed')
                k = keystore.from_seed(data['seed'], data['seed_extra_words'],
                                       data['wallet_type'] == 'multisig')
            elif data['seed_type'] == 'bip39':
                self._logger.debug('creating keystore from bip39 seed')
                root_seed = keystore.bip39_to_seed(data['seed'],
                                                   data['seed_extra_words'])
                derivation = normalize_bip32_derivation(
                    data['derivation_path'])
                script = data['script_type'] if data[
                    'script_type'] != 'p2pkh' else 'standard'
                k = keystore.from_bip43_rootseed(root_seed,
                                                 derivation,
                                                 xtype=script)
            else:
                raise Exception('unsupported/unknown seed_type %s' %
                                data['seed_type'])

            if data['encrypt']:
                if k.may_have_password():
                    k.update_password(None, data['password'])
                storage.set_password(
                    data['password'],
                    enc_version=StorageEncryptionVersion.USER_PASSWORD)

            db = WalletDB('', manual_upgrades=False)
            db.set_keystore_encryption(
                bool(data['password']) and data['encrypt'])

            db.put('wallet_type', data['wallet_type'])
            db.put('seed_type', data['seed_type'])
            db.put('keystore', k.dump())
            if k.can_have_deterministic_lightning_xprv():
                db.put(
                    'lightning_xprv',
                    k.get_lightning_xprv(
                        data['password'] if data['encrypt'] else None))

            db.load_plugins()
            db.write(storage)

            # minimally populate self after create
            self._password = data['password']
            self.path = path

            self.createSuccess.emit()
        except Exception as e:
            self._logger.error(str(e))
            self.createError.emit(str(e))