def handle_genesis_tx(self, tx): txid = tx.txid() try: slpMsg = SlpMessage.parseSlpOutputScript(tx.outputs()[0][1]) except SlpUnsupportedSlpTokenType as e: return self.fail_genesis_info( _("Unsupported SLP token version/type - %r.") % (e.args[0], )) except SlpInvalidOutputMessage as e: return self.fail_genesis_info( _("This transaction does not contain a valid SLP message.\nReason: %r." ) % (e.args, )) if slpMsg.transaction_type != 'GENESIS': return self.fail_genesis_info( _("This is an SLP transaction, however it is not a genesis transaction." )) slpMsg.op_return_fields['token_id'] = txid self.tokens.append(slpMsg.op_return_fields) dir = os.path.join(tempfile.gettempdir(), 'waifu') try: os.makedirs(dir) except: pass file = os.path.join(dir, f"{txid}.png") url = f"https://icons.waifufaucet.com/original/{txid}.png" if not os.path.isfile(file): urllib.request.urlretrieve(url, file) name = slpMsg.op_return_fields['token_name'].decode("utf-8") item = QListWidgetItem(QIcon(file), name) self.listWidget.addItem(item)
def build_slp_txn(coins, slp_output, receiver_output, postoffice_output, change_output): slp_msg = SlpMessage.parseSlpOutputScript(slp_output[1]) outputs = [slp_output, receiver_output] if len(slp_msg.op_return_fields["token_output"]) - 1 == 2: outputs.extend([postoffice_output]) elif len(slp_msg.op_return_fields["token_output"]) - 1 == 3: outputs.extend([postoffice_output, change_output]) tx = Transaction.from_io(coins, outputs) return tx
def build_slp_txn(coins, slp_output, pre_postage_outputs, postoffice_output, change_output, send_amount, old_slp_msg): slp_msg = SlpMessage.parseSlpOutputScript(slp_output[1]) pre_postage_outputs = list(pre_postage_outputs[1:]) if sum(old_slp_msg.op_return_fields["token_output"] ) > send_amount: # has change output pre_postage_outputs = pre_postage_outputs[:-1] outputs = [slp_output] + pre_postage_outputs if len(slp_msg.op_return_fields["token_output"]) - len( pre_postage_outputs) == 2: outputs.extend([postoffice_output]) elif len(slp_msg.op_return_fields["token_output"]) - len( pre_postage_outputs) == 3: outputs.extend([postoffice_output, change_output]) tx = Transaction.from_io(coins, outputs) return tx
def handle_genesis_tx(self, tx): self.token_id_e.setReadOnly(True) self.get_info_button.setDisabled(True) self.load_tx_menu_button.setDisabled(True) self.newtoken_genesis_tx = tx self.view_tx_button.setDisabled(False) txid = tx.txid() token_id = self.token_id_e.text().strip() if token_id and txid != token_id: return self.fail_genesis_info(_('TXID does not match token ID!')) self.newtoken_token_id = txid self.token_id_e.setText(self.newtoken_token_id) try: slpMsg = SlpMessage.parseSlpOutputScript(tx.outputs()[0][1]) except SlpUnsupportedSlpTokenType as e: return self.fail_genesis_info(_("Unsupported SLP token version/type - %r.")%(e.args[0],)) except SlpInvalidOutputMessage as e: return self.fail_genesis_info(_("This transaction does not contain a valid SLP message.\nReason: %r.")%(e.args,)) if slpMsg.transaction_type != 'GENESIS': return self.fail_genesis_info(_("This is an SLP transaction, however it is not a genesis transaction.")) f_fieldnames = QTextCharFormat() f_fieldnames.setFont(QFont(MONOSPACE_FONT)) f_normal = QTextCharFormat() self.token_info_e.clear() cursor = self.token_info_e.textCursor() fields = [ ('ticker', _('ticker'), 'utf8', None), ('token_name', _('name'), 'utf8', None), ('token_doc_url', _('doc url'), 'ascii', 'html'), ('token_doc_hash', _('doc hash'), 'hex', None), ] cursor.insertText(_('Issuer-declared strings in genesis:')) cursor.insertBlock() for k,n,e,f in fields: data = slpMsg.op_return_fields[k] if e == 'hex': friendlystring = None else: # Attempt to make a friendly string, or fail to hex try: # Ascii only friendlystring = data.decode(e) # raises UnicodeDecodeError with bytes > 127. # Count ugly characters (that need escaping in python strings' repr()) uglies = 0 for b in data: if b < 0x20 or b == 0x7f: uglies += 1 # Less than half of characters may be ugly. if 2*uglies >= len(data): friendlystring = None except UnicodeDecodeError: friendlystring = None if len(data) == 0: showstr = '(empty)' f=None elif friendlystring is None: showstr = data.hex() f=None else: showstr = repr(friendlystring) cursor.insertText(' '*(10 - len(n)) + n + ': ', f_fieldnames) if f == 'html': enc_url = html.escape(friendlystring) enc_text = html.escape(showstr) cursor.insertHtml('<a href="%s" title="%s">%s</a>'%(enc_url, enc_url, enc_text)) else: cursor.insertText(showstr, f_normal) cursor.insertBlock() self.newtoken_decimals = slpMsg.op_return_fields['decimals'] cursor.insertText(_('Decimals:') + ' ' + str(self.newtoken_decimals)) cursor.insertBlock() numtokens = format_satoshis_nofloat(slpMsg.op_return_fields['initial_token_mint_quantity'], num_zeros=self.newtoken_decimals, decimal_point=self.newtoken_decimals,) mbv = slpMsg.op_return_fields['mint_baton_vout'] if mbv is None or mbv > len(tx.outputs()): issuance_type = _('Initial issuance type: fixed supply') else: issuance_type = _('Initial issuance type: flexible supply') cursor.insertText(_('Initial issuance:') + ' ' + numtokens) cursor.insertBlock() cursor.insertText(issuance_type) #cursor.insertBlock() self.newtoken_genesis_message = slpMsg self.add_button.setDisabled(False)
def burn_token(self, preview=False, multisig_tx_to_sign=None): token_type = self.wallet.token_types[self.token_id_e.text()]['class'] unfrozen_token_qty = self.wallet.get_slp_token_balance( self.token_id_e.text(), self.main_window.config)[3] desired_burn_amt = self.token_qty_e.get_amount() selected_slp_coins = [] if desired_burn_amt == None or desired_burn_amt < 0: self.show_message(_("Enter a valid token quantity.")) return elif desired_burn_amt > unfrozen_token_qty: self.show_message( _("Cannot burn a quantity greater than your unfrozen token balance." )) return msg = "Destroy " + self.token_qty_e.text( ) + " " + self.token_name.text() + " tokens" if self.token_burn_baton_cb.isChecked(): msg += " AND the MINTING BATON" msg += "?!?" reply = QMessageBox.question(self, "Continue?", msg, QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: pass else: return outputs = [] addr = self.wallet.get_unused_address(frozen_ok=False) if addr is None: if not self.wallet.is_deterministic(): addr = self.wallet.get_receiving_address() else: addr = self.wallet.create_new_address(True) try: slp_coins = self.wallet.get_slp_utxos( self.token_id_e.text(), domain=None, exclude_frozen=True, confirmed_only=self.main_window.config.get( 'confirmed_only', False), slp_include_invalid= False, #self.token_burn_invalid_cb.isChecked(), slp_include_baton=self.token_burn_baton_cb.isChecked()) if multisig_tx_to_sign is None: selected_slp_coins = [] if desired_burn_amt < unfrozen_token_qty: total_amt_added = 0 for coin in slp_coins: if coin['token_value'] != "MINT_BATON" and coin[ 'token_validation_state'] == 1: if coin['token_value'] >= desired_burn_amt: selected_slp_coins.append(coin) total_amt_added += coin['token_value'] break if total_amt_added < desired_burn_amt: for coin in slp_coins: if coin['token_value'] != "MINT_BATON" and coin[ 'token_validation_state'] == 1: if total_amt_added < desired_burn_amt: selected_slp_coins.append(coin) total_amt_added += coin['token_value'] if total_amt_added > desired_burn_amt: slp_op_return_msg = buildSendOpReturnOutput_V1( self.token_id_e.text(), [total_amt_added - desired_burn_amt], token_type) outputs.append(slp_op_return_msg) outputs.append((TYPE_ADDRESS, addr, 546)) else: for coin in slp_coins: if coin['token_value'] != "MINT_BATON" and coin[ 'token_validation_state'] == 1: selected_slp_coins.append(coin) else: try: slp_msg = SlpMessage.parseSlpOutputScript( multisig_tx_to_sign.outputs()[0][1]) except SlpParsingError: slp_msg = None if slp_msg and slp_msg.op_return_fields[ 'token_id_hex'] != self.token_id_e.text(): self.show_message(_("Token id in the imported transaction is not correct.")+\ _("\n\nImported Token ID: ") + slp_msg.op_return_fields['token_id_hex'] + \ _("\n\nDesired Token ID: ") + self.token_id_e.text()) return for txo in multisig_tx_to_sign.inputs(): addr = txo['address'] prev_out = txo['prevout_hash'] prev_n = txo['prevout_n'] slp_txo = None try: for coin in slp_coins: if coin['prevout_hash'] == prev_out \ and coin['prevout_n'] == prev_n \ and coin['token_value'] != "MINT_BATON": selected_slp_coins.append(coin) total_burn_amt += coin['token_value'] except KeyError: pass if slp_msg: total_burn_amt -= sum( slp_msg.op_return_fields['token_output']) if total_burn_amt > desired_burn_amt: if slp_msg: self.show_message( _("Amount burned in transaction does not match the amount specified." )) else: self.show_message(_("Amount burned in transaction does not match the amount specified.") + \ _("\n\nMake sure the Token ID displayed in the Burn Tool dialog matches the token that you are trying to burn.")) return except OPReturnTooLarge: self.show_message( _("Optional string text causing OP_RETURN greater than 223 bytes." )) return except Exception as e: traceback.print_exc(file=sys.stdout) self.show_message(str(e)) return if self.token_burn_baton_cb.isChecked(): for coin in slp_coins: if coin['token_value'] == "MINT_BATON" and coin[ 'token_validation_state'] == 1: selected_slp_coins.append(coin) # if self.token_burn_invalid_cb.isChecked(): # for coin in slp_coins: # if coin['token_validation_state'] != 1: # selected_slp_coins.append(coin) try: if multisig_tx_to_sign is None: bch_change = sum(c['value'] for c in selected_slp_coins) outputs.append((TYPE_ADDRESS, addr, bch_change)) coins = self.main_window.get_coins() fixed_fee = None tx = self.main_window.wallet.make_unsigned_transaction( coins, outputs, self.main_window.config, fixed_fee, None, mandatory_coins=selected_slp_coins) else: tx = multisig_tx_to_sign # perform slp pre-flight check before signing (this check run here and also at signing) slp_preflight = SlpPreflightCheck.query( tx, selected_slp_coins=selected_slp_coins, amt_to_burn=desired_burn_amt) if not slp_preflight['ok']: raise Exception("slp pre-flight failed: %s" % slp_preflight['invalid_reason']) except NotEnoughFunds: self.show_message(_("Insufficient funds")) return except ExcessiveFee: self.show_message(_("Your fee is too high. Max is 50 sat/byte.")) return except BaseException as e: traceback.print_exc(file=sys.stdout) self.show_message(str(e)) return if preview: show_transaction(tx, self.main_window, None, False, self, slp_coins_to_burn=selected_slp_coins, slp_amt_to_burn=desired_burn_amt) return msg = [] if self.main_window.wallet.has_password(): msg.append("") msg.append(_("Enter your password to proceed")) password = self.main_window.password_dialog('\n'.join(msg)) if not password: return else: password = None tx_desc = None def sign_done(success): if success: if not tx.is_complete(): show_transaction(tx, self.main_window, None, False, self) self.main_window.do_clear() else: self.main_window.broadcast_transaction(tx, tx_desc) self.main_window.sign_tx_with_password( tx, sign_done, password, slp_coins_to_burn=selected_slp_coins, slp_amt_to_burn=desired_burn_amt) self.burn_button.setDisabled(True) self.close()
def __init__(self, tx, parent, desc, prompt_if_unsaved, window_to_close_on_broadcast=None, *, slp_coins_to_burn=None, slp_amt_to_burn=None): '''Transactions in the wallet will show their description. Pass desc to give a description for txs not yet in the wallet. ''' # We want to be a top-level window QDialog.__init__(self, parent=None) # Take a copy; it might get updated in the main window by # e.g. the FX plugin. If this happens during or after a long # sign operation the signatures are lost. self.tx = copy.deepcopy(tx) self.tx.deserialize() self.main_window = parent self.wallet = parent.wallet self.prompt_if_unsaved = prompt_if_unsaved self.window_to_close_on_broadcast = window_to_close_on_broadcast self.saved = False self.desc = desc self.cashaddr_signal_slots = [] self._dl_pct = None self._closed = False self.tx_hash = self.tx.txid_fast() if self.tx.raw and self.tx.is_complete() else None self.slp_token_id_label = None self.tx_height = None self.slp_coins_to_burn = slp_coins_to_burn self.slp_amt_to_burn = slp_amt_to_burn # Parse SLP output data self.slp_outputs = [] self.slp_mint_baton_vout = None self.slp_info = None try: slp_msg = SlpMessage.parseSlpOutputScript(tx.outputs()[0][1]) except: pass else: self.slp_info = slp_msg.op_return_fields self.slp_info['type'] = slp_msg.transaction_type if self.slp_info['type'] == "GENESIS": if self.tx_hash is not None: wallet_dat = self.wallet.token_types[self.tx_hash] self.slp_info['token_id_hex'] = self.tx_hash else: wallet_dat = {} wallet_dat['decimals'] = slp_msg.op_return_fields['decimals'] wallet_dat['name'] = "Not in wallet" self.slp_info['token_id_hex'] = "Unknown until transaction is complete" else: try: wallet_dat = self.wallet.token_types[slp_msg.op_return_fields['token_id_hex']] except KeyError: wallet_dat = {} wallet_dat['decimals'] = "?" wallet_dat['name'] = "Not in wallet" self.slp_info['name'] = wallet_dat['name'] dec = wallet_dat['decimals'] if isinstance(dec, int): if self.slp_info['type'] == "GENESIS": self.slp_outputs.append(0) mint = self.slp_info['initial_token_mint_quantity'] self.slp_outputs.append(format_satoshis_nofloat(mint, decimal_point=dec, num_zeros=dec)) self.slp_mint_baton_vout = self.slp_info['mint_baton_vout'] elif self.slp_info['type'] == "MINT": self.slp_outputs.append(0) mint = self.slp_info['additional_token_quantity'] self.slp_outputs.append(format_satoshis_nofloat(mint, decimal_point=dec, num_zeros=dec)) self.slp_mint_baton_vout = self.slp_info['mint_baton_vout'] elif self.slp_info['type'] == "SEND": for i, o in enumerate(self.slp_info['token_output']): self.slp_outputs.append(format_satoshis_nofloat(o, decimal_point=dec, num_zeros=dec)) Weak.finalization_print_error(self) # track object lifecycle self.setMinimumWidth(750) self.setWindowTitle(_("Transaction")) vbox = QVBoxLayout() self.setLayout(vbox) self.tx_hash_e = ButtonsLineEdit() l = QLabel(_("&Transaction ID:")) l.setBuddy(self.tx_hash_e) vbox.addWidget(l) self.tx_hash_e.addCopyButton() weakSelfRef = Weak.ref(self) qr_show = lambda: weakSelfRef() and weakSelfRef().main_window.show_qrcode(str(weakSelfRef().tx_hash_e.text()), _("Transaction ID"), parent=weakSelfRef()) icon = ":icons/qrcode_white.svg" if ColorScheme.dark_scheme else ":icons/qrcode.svg" self.tx_hash_e.addButton(icon, qr_show, _("Show as QR code")) self.tx_hash_e.setReadOnly(True) vbox.addWidget(self.tx_hash_e) self.tx_desc = QLabel() vbox.addWidget(self.tx_desc) self.status_label = QLabel() vbox.addWidget(self.status_label) self.date_label = QLabel() vbox.addWidget(self.date_label) self.amount_label = QLabel() vbox.addWidget(self.amount_label) self.size_label = QLabel() vbox.addWidget(self.size_label) self.fee_label = QLabel() vbox.addWidget(self.fee_label) for l in (self.tx_desc, self.status_label, self.date_label, self.amount_label, self.size_label, self.fee_label): # make these labels selectable by mouse in case user wants to copy-paste things in tx dialog l.setTextInteractionFlags(l.textInteractionFlags() | Qt.TextSelectableByMouse) def open_be_url(link): if link: try: kind, thing = link.split(':') url = web.BE_URL(self.main_window.config, kind, thing) except: url = None if url: webopen( url ) else: self.show_error(_('Unable to open in block explorer. Please be sure your block explorer is configured correctly in preferences.')) self.status_label.linkActivated.connect(open_be_url) self.add_io(vbox) self.add_slp_info(vbox) self.sign_button = b = QPushButton(_("&Sign")) b.clicked.connect(self.sign) self.broadcast_button = b = QPushButton(_("&Broadcast")) b.clicked.connect(self.do_broadcast) self.last_broadcast_time = 0 self.save_button = b = QPushButton(_("S&ave")) b.clicked.connect(self.save) self.cancel_button = b = CloseButton(self) self.qr_button = b = QPushButton() b.setIcon(QIcon(icon)) b.clicked.connect(self.show_qr) b.setShortcut(QKeySequence(Qt.ALT + Qt.Key_Q)) self.copy_button = CopyButton(lambda: str(weakSelfRef() and weakSelfRef().tx), callback=lambda: weakSelfRef() and weakSelfRef().show_message(_("Transaction raw hex copied to clipboard."))) # Action buttons self.buttons = [self.sign_button, self.broadcast_button, self.cancel_button] # Transaction sharing buttons self.sharing_buttons = [self.copy_button, self.qr_button, self.save_button] run_hook('transaction_dialog', self) hbox = QHBoxLayout() hbox.addLayout(Buttons(*self.sharing_buttons)) hbox.addStretch(1) hbox.addLayout(Buttons(*self.buttons)) vbox.addLayout(hbox) self.throttled_update_sig.connect(self.throttled_update, Qt.QueuedConnection) self.initiate_fetch_input_data(True) self.update() # connect slots so we update in realtime as blocks come in, etc parent.history_updated_signal.connect(self.update_tx_if_in_wallet) parent.labels_updated_signal.connect(self.update_tx_if_in_wallet) parent.network_signal.connect(self.got_verified_tx)