def read_invoice(self): address = str(self.address) if not address: self.app.show_error(_('Recipient not specified.') + ' ' + _('Please scan a Actinium address or a payment request')) return if not self.amount: self.app.show_error(_('Please enter an amount')) return try: amount = self.app.get_amount(self.amount) except: self.app.show_error(_('Invalid amount') + ':\n' + self.amount) return message = self.message if self.is_lightning: return parse_lightning_invoice(address) else: # on-chain if self.payment_request: outputs = self.payment_request.get_outputs() else: if not bitcoin.is_address(address): self.app.show_error(_('Invalid Actinium Address') + ':\n' + address) return outputs = [PartialTxOutput.from_address_and_value(address, amount)] return self.app.wallet.create_invoice(outputs, message, self.payment_request, self.parsed_URI)
def update_action_button(self): action_button = self.ids.action_button options = ( ActionButtonOption(text=_('Sign'), func=lambda btn: self.do_sign(), enabled=self.can_sign), ActionButtonOption(text=_('Broadcast'), func=lambda btn: self.do_broadcast(), enabled=self.can_broadcast), ActionButtonOption(text=_('Bump fee'), func=lambda btn: self.do_rbf(), enabled=self.can_rbf), ActionButtonOption(text=_('Remove'), func=lambda btn: self.remove_local_tx(), enabled=self.can_remove_tx), ) num_options = sum(map(lambda o: bool(o.enabled), options)) # if no options available, hide button if num_options == 0: action_button.disabled = True action_button.opacity = 0 return action_button.disabled = False action_button.opacity = 1 if num_options == 1: # only one option, button will correspond to that for option in options: if option.enabled: action_button.text = option.text self._action_button_fn = option.func else: # multiple options. button opens dropdown which has one sub-button for each dropdown = DropDown() action_button.text = _('Options') self._action_button_fn = dropdown.open for option in options: if option.enabled: btn = Button(text=option.text, size_hint_y=None, height='48dp') btn.bind(on_release=option.func) dropdown.add_widget(btn)
def do_open_channel(self, conn_str, amount, password): coins = self.app.wallet.get_spendable_coins(None, nonlocal_only=True) funding_tx = self.app.wallet.lnworker.mktx_for_open_channel( coins=coins, funding_sat=amount) try: chan, funding_tx = self.app.wallet.lnworker.open_channel( connect_str=conn_str, funding_tx=funding_tx, funding_sat=amount, push_amt_sat=0, password=password) except Exception as e: self.app.show_error( _('Problem opening channel: ') + '\n' + repr(e)) return n = chan.constraints.funding_txn_minimum_depth message = '\n'.join([ _('Channel established.'), _('Remote peer ID') + ':' + chan.node_id.hex(), _('This channel will be usable after {} confirmations').format(n) ]) if not funding_tx.is_complete(): message += '\n\n' + _( 'Please sign and broadcast the funding transaction') self.app.show_info(message) if not funding_tx.is_complete(): self.app.tx_dialog(funding_tx)
def get_card(self, addr, balance, is_used, label): ci = {} ci['screen'] = self ci['address'] = addr ci['memo'] = label ci['amount'] = self.app.format_amount_and_units(balance) ci['status'] = _('Used') if is_used else _( 'Funded') if balance > 0 else _('Unused') return ci
def update(self): self.menu_actions = [(_('Show'), self.do_show), (_('Delete'), self.do_delete)] requests_list = self.ids.requests_container requests_list.clear_widgets() _list = self.app.wallet.get_sorted_requests( self.app.actilectrum_config) for pr in _list: ci = self.get_card(pr) requests_list.add_widget(ci)
def update_status(self): req = self.app.wallet.get_request(self.key) self.status, self.status_str = get_request_status(req) self.status_color = pr_color[self.status] if self.status == PR_UNPAID and self.is_lightning and self.app.wallet.lnworker: if self.amount and self.amount > self.app.wallet.lnworker.num_sats_can_receive( ): self.warning = _('Warning') + ': ' + _( 'This amount exceeds the maximum you can currently receive with your channels' )
def update_status(self): req = self.app.wallet.get_invoice(self.key) self.status, self.status_str = get_request_status(req) self.status_color = pr_color[self.status] self.can_pay = self.status in [PR_UNPAID, PR_FAILED] if self.can_pay and self.is_lightning and self.app.wallet.lnworker: if self.amount and self.amount > self.app.wallet.lnworker.num_sats_can_send( ): self.warning = _('Warning') + ': ' + _( 'This amount exceeds the maximum you can currently send with your channels' )
class PincodeDialog(AbstractPasswordDialog, Factory.Popup): enter_pw_message = _('Enter your PIN') enter_new_pw_message = _('Enter new PIN') confirm_new_pw_message = _('Confirm new PIN') wrong_password_message = _('Wrong PIN') allow_disable = True def clear_password(self): self.ids.kb.password = '' def on_password(self, pw: str): # PIN codes are exactly 6 chars if len(pw) >= 6: self.do_check(pw)
def ipport_dialog(self): def callback(text): self.ipport = text d = LabelDialog(_('IP/port in format:\n[host]:[port]'), self.ipport, callback) d.open()
def show_log(self): if self.log: log_str = _('Payment log:') + '\n\n' for payment_attempt_log in self.log: route_str, chan_str, message = payment_attempt_log.formatted_tuple( ) log_str += chan_str + ' --- ' + message + '\n' self.app.show_info(log_str)
def fx_status(self): fx = self.app.fx if fx.is_enabled(): source = fx.exchange.name() ccy = fx.get_currency() return '%s [%s]' % (ccy, source) else: return _('None')
def _do_pay_onchain(self, invoice, rbf): # make unsigned transaction outputs = invoice['outputs'] # type: List[PartialTxOutput] amount = sum(map(lambda x: x.value, outputs)) coins = self.app.wallet.get_spendable_coins(None) try: tx = self.app.wallet.make_unsigned_transaction(coins=coins, outputs=outputs) except NotEnoughFunds: self.app.show_error(_("Not enough funds")) return except Exception as e: Logger.exception('') self.app.show_error(repr(e)) return if rbf: tx.set_rbf(True) fee = tx.get_fee() msg = [ _("Amount to be sent") + ": " + self.app.format_amount_and_units(amount), _("Mining fee") + ": " + self.app.format_amount_and_units(fee), ] x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx) if x_fee: x_fee_address, x_fee_amount = x_fee msg.append(_("Additional fees") + ": " + self.app.format_amount_and_units(x_fee_amount)) feerate_warning = simple_config.FEERATE_WARNING_HIGH_FEE if fee > feerate_warning * tx.estimated_size() / 1000: msg.append(_('Warning') + ': ' + _("The fee for this transaction seems unusually high.")) msg.append(_("Enter your PIN code to proceed")) self.app.protected('\n'.join(msg), self.send_tx, (tx,))
def do_delete(self, obj): from .question import Question def cb(result): if result: self.app.wallet.invoices.remove(obj.key) self.hide_menu() self.update() d = Question(_('Delete invoice?'), cb) d.open()
def remove_local_tx(self): txid = self.tx.txid() to_delete = {txid} to_delete |= self.wallet.get_depending_transactions(txid) question = _("Are you sure you want to remove this transaction?") if len(to_delete) > 1: question = (_("Are you sure you want to remove this transaction and {} child transactions?") .format(len(to_delete) - 1)) def on_prompt(b): if b: for tx in to_delete: self.wallet.remove_transaction(tx) self.wallet.save_db() self.app._trigger_update_wallet() # FIXME private... self.dismiss() d = Question(question, on_prompt) d.open()
def on_address(self, addr): req = self.app.wallet.get_request(addr) self.status = '' if req: self.message = req.get('memo', '') amount = req.get('amount') self.amount = self.app.format_amount_and_units(amount) if amount else '' status = req.get('status', PR_UNKNOWN) self.status = _('Payment received') if status == PR_PAID else ''
def on_password(self, pw: str): # if setting new generic password, enforce min length if self.level > 0: if len(pw) < 6: self.app.show_error( _('Password is too short (min {} characters)').format(6)) return # don't enforce minimum length on existing self.do_check(pw)
def do_rbf(self): from .bump_fee_dialog import BumpFeeDialog is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(self.tx) if fee is None: self.app.show_error(_("Can't bump fee: unknown fee for original transaction.")) return size = self.tx.estimated_size() d = BumpFeeDialog(self.app, fee, size, self._do_rbf) d.open()
def on_currency(self, ccy): b = (ccy != _('None')) self.fx.set_enabled(b) if b: if ccy != self.fx.get_currency(): self.fx.set_currency(ccy) self.app.fiat_unit = ccy else: self.app.is_fiat = False Clock.schedule_once(lambda dt: self.add_exchanges())
def label_dialog(self): from .label_dialog import LabelDialog key = self.tx.txid() text = self.app.wallet.get_label(key) def callback(text): self.app.wallet.set_label(key, text) self.update() self.app.history_screen.update() d = LabelDialog(_('Enter Transaction Label'), text, callback) d.open()
def delete_dialog(self): from .question import Question def cb(result): if result: self.app.wallet.delete_request(self.key) self.dismiss() self.app.receive_screen.update() d = Question(_('Delete request?'), cb) d.open()
def open_channel(self): if not self.pubkey or not self.amount: self.app.show_info(_('All fields must be filled out')) return conn_str = self.pubkey if self.ipport: conn_str += '@' + self.ipport.strip() amount = self.app.get_amount(self.amount) self.app.protected('Enter PIN to create a new channel', self.do_open_channel, (conn_str, amount)) self.dismiss()
def __init__(self, parent, address, balance, status, **kwargs): super(AddressPopup, self).__init__(**kwargs) self.title = _('Address Details') self.parent_dialog = parent self.app = parent.app self.address = address self.status = status self.script_type = self.app.wallet.get_txin_type(self.address) self.balance = self.app.format_amount_and_units(balance) self.address_color, self.address_background_color = address_colors( self.app.wallet, address)
def delete_dialog(self): from .question import Question def cb(result): if result: self.app.wallet.delete_invoice(self.key) self.dismiss() self.app.send_screen.update() d = Question(_('Delete invoice?'), cb) d.open()
def show_qr(self): from actilectrum.bitcoin import base_encode, bfh original_raw_tx = str(self.tx) tx = copy.deepcopy(self.tx) # make copy as we mutate tx if isinstance(tx, PartialTransaction): # this makes QR codes a lot smaller (or just possible in the first place!) tx.convert_all_utxos_to_witness_utxos() text = tx.serialize_as_bytes() text = base_encode(text, base=43) self.app.qr_dialog(_("Raw Transaction"), text, text_for_clipboard=original_raw_tx)
def language_dialog(self, item, dt): if self._language_dialog is None: l = self.config.get('language', 'en_UK') def cb(key): self.config.set_key("language", key, True) item.lang = self.get_language_name() self.app.language = key self._language_dialog = ChoiceDialog(_('Language'), languages, l, cb) self._language_dialog.open()
def do_delete(self, req): from .question import Question def cb(result): if result: self.app.wallet.remove_payment_request( req.address, self.app.actilectrum_config) self.hide_menu() self.update() d = Question(_('Delete request'), cb) d.open()
def coinselect_dialog(self, item, dt): if self._coinselect_dialog is None: choosers = sorted(coinchooser.COIN_CHOOSERS.keys()) chooser_name = coinchooser.get_name(self.config) def cb(text): self.config.set_key('coin_chooser', text) item.status = text self._coinselect_dialog = ChoiceDialog(_('Coin selection'), choosers, chooser_name, cb) self._coinselect_dialog.open()
def __init__(self, obj, action_list): Bubble.__init__(self) self.obj = obj for k, v in action_list: l = MenuItem() l.text = _(k) def func(f=v): Clock.schedule_once(lambda dt: f(obj), 0.15) l.on_release = func self.ids.buttons.add_widget(l)
def set_ln_invoice(self, invoice): try: invoice = str(invoice).lower() lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP) except Exception as e: self.app.show_info(invoice + _(" is not a valid Lightning invoice: ") + repr(e)) # repr because str(Exception()) == '' return self.address = invoice self.message = dict(lnaddr.tags).get('d', None) self.amount = self.app.format_amount_and_units(lnaddr.amount * bitcoin.COIN) if lnaddr.amount else '' self.payment_request = None self.is_lightning = True
class PasswordDialog(AbstractPasswordDialog, Factory.Popup): enter_pw_message = _('Enter your password') enter_new_pw_message = _('Enter new password') confirm_new_pw_message = _('Confirm new password') wrong_password_message = _('Wrong password') allow_disable = False def clear_password(self): self.ids.textinput_generic_password.text = '' def on_password(self, pw: str): # if setting new generic password, enforce min length if self.level > 0: if len(pw) < 6: self.app.show_error( _('Password is too short (min {} characters)').format(6)) return # don't enforce minimum length on existing self.do_check(pw) def select_file(self): self.app.wallets_dialog()