class TxDeserializer(BaseDock): def __init__(self, handler): super(TxDeserializer, self).__init__(handler) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.context_menu) def init_metadata(self): self.tool_name = 'Transaction Deserializer' self.description = 'Deserializes transactions.' def init_data(self): self.tx = None def create_layout(self): form = QFormLayout() form.setRowWrapPolicy(QFormLayout.WrapLongRows) self.raw_tx_edit = QPlainTextEdit() self.raw_tx_edit.setFont(monospace_font) self.deserialize_button = QPushButton('Deserialize') self.deserialize_button.clicked.connect(self.deserialize) btn_hbox = floated_buttons([self.deserialize_button]) self.tx_widget = TxWidget() form.addRow('Raw Tx:', self.raw_tx_edit) form.addRow(btn_hbox) form.addRow(Separator()) form.addRow(self.tx_widget) return form def context_menu(self, position): menu = QMenu() menu.addAction('Clear Fields', self.clear) set_spend = menu.addAction('Set as spending transaction in Stack Evaluator', self.set_as_spending_tx) set_spend.setEnabled(True if self.tx else False) menu.exec_(self.mapToGlobal(position)) def set_as_spending_tx(self): txt = str(self.raw_tx_edit.toPlainText()) self.handler.set_stack_spending_tx(txt) def clear(self): self.tx_widget.clear() def deserialize(self): self.clear() txt = str(self.raw_tx_edit.toPlainText()) try: txt = txt.decode('hex') except Exception: self.status_message('Raw transaction must be hex.', True) return try: self.tx = tx = CTransaction.deserialize(txt) except Exception: self.status_message('Cannot deserialize transaction.', True) return self.tx_widget.set_tx(tx) self.status_message('Deserialized transaction {}'.format(bitcoin.core.b2lx(tx.GetHash()))) def on_option_changed(self, key): if key == 'amount_format': self.needsUpdate.emit() def refresh_data(self): self.deserialize()
class TxAnalyzer(BaseDock): tool_name = 'Transaction Analyzer' description = 'Deserializes transactions and verifies their inputs.' is_large = True category = Category.Tx def __init__(self, handler): super(TxAnalyzer, self).__init__(handler) self.raw_tx_edit.textChanged.emit() def init_data(self): self.tx = None def init_actions(self): deserialize = ('Deserialize', self.deserialize_raw) verify = ('Verify inputs', self.do_verify_inputs) self.advertised_actions['raw_transaction'] = [deserialize, verify] def create_layout(self): form = QFormLayout() self.raw_tx_edit = QPlainTextEdit() self.raw_tx_edit.setFont(monospace_font) self.raw_tx_edit.setContextMenuPolicy(Qt.CustomContextMenu) self.raw_tx_edit.customContextMenuRequested.connect(self.context_menu) self.raw_tx_edit.textChanged.connect(self.check_raw_tx) self.raw_tx_invalid = QLabel('Cannot parse transaction.') self.raw_tx_invalid.setProperty('hasError', True) self.tabs = tabs = QTabWidget() tabs.addTab(self.create_deserialize_tab(), 'Deserialize') tabs.addTab(self.create_verify_tab(), 'Verify') tabs.setTabToolTip(0, 'View the transaction in human-readable form') tabs.setTabToolTip(1, 'Download previous transactions and verify inputs') form.addRow('Raw Tx:', self.raw_tx_edit) form.addRow(self.raw_tx_invalid) form.addRow(tabs) return form def create_deserialize_tab(self): form = QFormLayout() self.deserialize_button = QPushButton('Deserialize') self.deserialize_button.clicked.connect(self.deserialize) btn_hbox = floated_buttons([self.deserialize_button]) self.tx_widget = TxWidget() self.tx_widget.inputs_tree.view.customContextMenuRequested.disconnect( self.tx_widget.inputs_tree.customContextMenu) self.tx_widget.inputs_tree.view.customContextMenuRequested.connect( self.inputs_context_menu) form.addRow(self.tx_widget) w = QWidget() w.setLayout(form) return w def create_verify_tab(self): form = QFormLayout() form.setRowWrapPolicy(QFormLayout.WrapLongRows) self.inputs_box = QSpinBox() self.verify_button = QPushButton('Verify') self.verify_button.clicked.connect(self.verify_input) self.verify_all_button = QPushButton('Verify All') self.verify_all_button.clicked.connect(self.verify_inputs) self.result_edit = QLineEdit() self.result_edit.setReadOnly(True) self.inputs_table = InputStatusTable() form.addRow('Verify Input:', floated_buttons([self.inputs_box, self.verify_button])) form.addRow(floated_buttons([self.verify_all_button])) form.addRow('Result:', self.result_edit) form.addRow(self.inputs_table) w = QWidget() w.setLayout(form) return w def context_menu(self, position): menu = QMenu() if self.tx: txt = str(self.raw_tx_edit.toPlainText()) self.handler.add_plugin_actions(self, menu, 'raw_transaction', txt) menu.exec_(self.mapToGlobal(position)) def inputs_context_menu(self, position): inputs = self.tx_widget.inputs_tree def inputs_context_verify(): self.tabs.setCurrentIndex(1) item = inputs.model.itemFromIndex(inputs.view.selectedIndexes()[0]) row = item.row() self.do_verify_input(self.tx, row) menu = QMenu() if self.tx: menu = inputs.context_menu() menu.addAction('Verify script', inputs_context_verify) menu.exec_(inputs.view.viewport().mapToGlobal(position)) def clear(self): self.result_edit.clear() self.tx_widget.clear() self.inputs_table.clear() def check_raw_tx(self): txt = str(self.raw_tx_edit.toPlainText()) tx = None valid = True try: tx = Transaction.deserialize(txt.decode('hex')) except Exception: valid = False self.tx = tx self.deserialize_button.setEnabled(valid) self.inputs_box.setEnabled(valid) self.verify_button.setEnabled(valid) self.verify_all_button.setEnabled(valid) self.tx_widget.setEnabled(valid) self.clear() if valid: self.raw_tx_invalid.hide() self.inputs_box.setRange(0, len(tx.vin) - 1) self.deserialize() elif txt: self.raw_tx_invalid.show() else: self.raw_tx_invalid.hide() def deserialize_raw(self, txt): """Deserialize a raw transaction.""" self.needsFocus.emit() self.raw_tx_edit.setPlainText(txt) self.deserialize() def deserialize(self): self.clear() self.tx_widget.set_tx(self.tx) self.inputs_table.set_tx(self.tx) self.status_message('Deserialized transaction {}'.format( bitcoin.core.b2lx(self.tx.GetHash()))) def do_verify_input(self, tx, in_idx): raw_prev_tx = None tx_in = tx.vin[in_idx] txid = b2lx(tx_in.prevout.hash) prev_out_n = tx_in.prevout.n try: raw_prev_tx = self.handler.download_blockchain_data( 'raw_transaction', txid) except Exception as e: self.status_message(str(e), True) return False try: prev_tx = Transaction.deserialize(raw_prev_tx.decode('hex')) result = bitcoin.core.scripteval.VerifyScript( tx_in.scriptSig, prev_tx.vout[prev_out_n].scriptPubKey, tx, in_idx) self.result_edit.setText( 'Successfully verified input {}'.format(in_idx)) self.inputs_table.set_verified(in_idx, True) except Exception as e: self.result_edit.setText(str(e)) self.inputs_table.set_verified(in_idx, False) self.status_message(str(e), True) return False return True def do_verify_inputs(self, txt): self.needsFocus.emit() self.raw_tx_edit.setPlainText(txt) tx = Transaction.deserialize(txt.decode('hex')) failed_inputs = [] self.result_edit.setText('Verifying...') for i in range(len(tx.vin)): if not self.do_verify_input(tx, i): failed_inputs.append(i) result = 'Successfully verified all inputs.' ret_val = True if failed_inputs: result = 'Failed to verify inputs: {}'.format(failed_inputs) ret_val = False if len(tx.vin) == 0: result = 'Transaction has no inputs.' self.result_edit.setText(result) return ret_val def verify_input(self): tx = None try: txt = str(self.raw_tx_edit.toPlainText()) tx = Transaction.deserialize(txt.decode('hex')) except Exception: self.status_message('Could not deserialize transaction.', True) return in_idx = self.inputs_box.value() if in_idx >= len(tx.vin): self.status_message('Input {} does not exist.'.format(in_idx), True) return self.do_verify_input(tx, in_idx) def verify_inputs(self): txt = str(self.raw_tx_edit.toPlainText()) self.do_verify_inputs(txt) def on_option_changed(self, key): if key == 'amount_format': self.needsUpdate.emit() def refresh_data(self): if self.tx: self.deserialize()
class TxBuilder(BaseDock): tool_name = 'Transaction Builder' description = 'Transaction Builder helps you create transactions.' is_large = True category = Category.Tx def __init__(self, handler): super(TxBuilder, self).__init__(handler) self.raw_tx.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.raw_tx.customContextMenuRequested.connect(self.context_menu) def init_data(self): self.tx = None def create_layout(self): vbox = QVBoxLayout() self.tabs = tabs = QTabWidget() tabs.addTab(self.create_version_locktime_tab(), '&Version/Locktime') tabs.addTab(self.create_inputs_tab(), '&Inputs') tabs.addTab(self.create_outputs_tab(), '&Outputs') tabs.addTab(self.create_review_tab(), '&Review') self.tx_field_widgets = [] tabs.insertTab(3, self.create_other_tab(), 'Ot&her') self.adjust_tx_fields() # Build the tx if the Review tab is selected. def maybe_build(i): if str(tabs.tabText(i)) == '&Review': self.build_transaction() tabs.currentChanged.connect(maybe_build) vbox.addWidget(tabs) return vbox def context_menu(self, position): menu = self.raw_tx.createStandardContextMenu(position) txt = str(self.raw_tx.toPlainText()) if txt: self.handler.add_plugin_actions(self, menu, 'raw_transaction', txt) menu.exec_(self.raw_tx.viewport().mapToGlobal(position)) def create_version_locktime_tab(self): form = QFormLayout() self.version_edit = AmountEdit() self.version_edit.setText('1') self.locktime_edit = AmountEdit() self.locktime_edit.setText('0') version_desc = QLabel('A transaction\'s version determines how it is interpreted.\n\nBitcoin transactions are currently version 1.') locktime_desc = QLabel('A transaction\'s locktime defines the earliest time or block that it may be added to the blockchain.\n\nLocktime only applies if it\'s non-zero and at least one input has a Sequence that\'s not the maximum possible value.') for i in [version_desc, locktime_desc]: i.setWordWrap(True) for i in [self.version_edit, self.locktime_edit]: i.setFont(monospace_font) form.addRow(version_desc) form.addRow('Version:', self.version_edit) form.addRow(Separator()) form.addRow(locktime_desc) form.addRow('Locktime:', self.locktime_edit) w = QWidget() w.setLayout(form) return w def create_inputs_tab(self): form = QFormLayout() self.inputs_tree = InputsTree() input_prev_tx = QLineEdit() input_prev_tx.setToolTip('Transaction ID of the tx with the output being spent') input_prev_vout = AmountEdit() input_prev_vout.setToolTip('Output index of the previous transaction') input_script = QTextEdit() input_script.setToolTip('Script that will be put on the stack before the previous output\'s script.') input_sequence = AmountEdit() input_sequence.setText('4294967295') maxify_input_sequence = QPushButton('Max') maxify_input_sequence.clicked.connect(lambda: input_sequence.setText('0xffffffff')) rm_input_edit = QSpinBox() rm_input_edit.setRange(0, 0) rm_input_button = QPushButton('Remove input') def add_input(): try: outpoint = COutPoint(lx(str(input_prev_tx.text())), input_prev_vout.get_amount()) in_script = Script.from_human(str(input_script.toPlainText())) new_input = CTxIn(outpoint, in_script.get_hex().decode('hex'), input_sequence.get_amount()) except Exception as e: self.status_message(str(e), True) return else: self.inputs_tree.add_input(new_input) rm_input_edit.setRange(0, len(self.inputs_tree.get_inputs()) - 1) def rm_input(): in_num = rm_input_edit.value() self.inputs_tree.model.takeRow(in_num) rm_input_edit.setRange(0, len(self.inputs_tree.get_inputs()) - 1) add_input_button = QPushButton('Add input') add_input_button.setToolTip('Add the above input') add_input_button.clicked.connect(add_input) rm_input_button.clicked.connect(rm_input) for i in [input_prev_tx, input_prev_vout, input_script, input_sequence]: i.setFont(monospace_font) form.addRow(self.inputs_tree) form.addRow(Separator()) form.addRow('Previous Transaction:', input_prev_tx) form.addRow('Previous Tx Output:', input_prev_vout) form.addRow('Input script:', input_script) seq_desc = QLabel('Sequence is mostly deprecated.\nIf an input has a sequence that\'s not the maximum value, the transaction\'s locktime will apply.') seq_desc.setWordWrap(True) form.addRow(seq_desc) form.addRow('Sequence:', HBox(input_sequence, maxify_input_sequence)) form.addRow(Separator()) form.addRow(floated_buttons([add_input_button])) form.addRow('Remove input:', HBox(rm_input_edit, rm_input_button)) w = QWidget() w.setLayout(form) return w def create_outputs_tab(self): form = QFormLayout() self.outputs_tree = OutputsTree() output_value = QLineEdit() output_script = QTextEdit() output_script.setToolTip('Script that will be put on the stack after the input that spends it.') rm_output_edit = QSpinBox() rm_output_edit.setRange(0, 0) rm_output_button = QPushButton('Remove output') def add_output(): try: val_str = str(output_value.text()) value = 0 if '.' in val_str: value = int(float(val_str) * pow(10, 8)) else: value = int(val_str) out_script = Script.from_human(str(output_script.toPlainText())) new_output = CTxOut(value, out_script.get_hex().decode('hex')) except Exception as e: self.status_message(str(e), True) return else: self.outputs_tree.add_output(new_output) rm_output_edit.setRange(0, len(self.outputs_tree.get_outputs()) - 1) def rm_output(): out_n = rm_output_edit.value() self.outputs_tree.model.takeRow(out_n) rm_output_edit.setRange(0, len(self.outputs_tree.get_outputs()) - 1) add_output_button = QPushButton('Add output') add_output_button.setToolTip('Add the above output') add_output_button.clicked.connect(add_output) rm_output_button.clicked.connect(rm_output) value_desc = QLabel('Include a decimal point if this value is not in satoshis.') value_desc.setWordWrap(True) for i in [output_value, output_script]: i.setFont(monospace_font) form.addRow(self.outputs_tree) form.addRow(Separator()) form.addRow(value_desc) form.addRow('Value:', output_value) form.addRow('Output script:', output_script) form.addRow(Separator()) form.addRow(floated_buttons([add_output_button])) form.addRow('Remove output:', HBox(rm_output_edit, rm_output_button)) w = QWidget() w.setLayout(form) return w def create_review_tab(self): form = QFormLayout() self.raw_tx = QTextEdit() self.raw_tx.setReadOnly(True) self.tx_widget = TxWidget() build_button = QPushButton('Build transaction') build_button.setToolTip('Build a tx from the data in the previous tabs') build_button.clicked.connect(self.build_transaction) form.addRow('Raw Tx:', self.raw_tx) form.addRow(self.tx_widget) form.addRow(floated_buttons([build_button])) w = QWidget() w.setLayout(form) return w def create_other_tab(self): self.tx_fields_layout = QFormLayout() w = QWidget() w.setLayout(self.tx_fields_layout) return w def build_transaction(self): self.tx_widget.clear() self.tx = tx = Transaction() tx.nVersion = self.version_edit.get_amount() for i in self.inputs_tree.get_inputs(): tx.vin.append(i) for o in self.outputs_tree.get_outputs(): tx.vout.append(o) tx.nLockTime = self.locktime_edit.get_amount() for name, w in self.tx_field_widgets: if not name in [field[0] for field in tx.fields]: continue value = str(w.text()) default = getattr(tx, name) if isinstance(default, int): value = w.get_amount() setattr(tx, name, value) self.raw_tx.setText(bitcoin.core.b2x(tx.serialize())) self.tx_widget.set_tx(tx) def on_option_changed(self, key): if key in ['amount_format', 'chainparams']: self.needsUpdate.emit() def adjust_tx_fields(self): """Show or hide tx field widgets.""" tx_fields = chainparams.get_tx_fields() for field in tx_fields: name = field[0] if name in ['nVersion', 'vin', 'vout', 'nLockTime']: continue default_value = field[3] if name not in [j[0] for j in self.tx_field_widgets]: widget = QLineEdit() if isinstance(default_value, int): # Special case for timestamp fields. if name == 'Timestamp': widget = TimestampWidget() widget.timestamp_raw.setReadOnly(False) else: widget = AmountEdit() widget.setText(str(default_value)) label = QLabel(''.join([name, ':'])) self.tx_field_widgets.append((name, widget)) self.tx_fields_layout.addRow(label, widget) tx_field_names = [i[0] for i in tx_fields] for name, w in self.tx_field_widgets: l = self.tx_fields_layout.labelForField(w) if name in tx_field_names: w.show() l.show() else: w.hide() l.hide() if tx_field_names == ['nVersion', 'vin', 'vout', 'nLockTime']: self.tabs.setTabEnabled(3, False) else: self.tabs.setTabEnabled(3, True) def refresh_data(self): self.adjust_tx_fields() self.build_transaction() self.outputs_tree.amount_format_changed()
class TxAnalyzer(BaseDock): tool_name = 'Transaction Analyzer' description = 'Deserializes transactions and verifies their inputs.' is_large = True category = Category.Tx def __init__(self, handler): super(TxAnalyzer, self).__init__(handler) self.raw_tx_edit.textChanged.emit() def init_data(self): self.tx = None def init_actions(self): deserialize = ('Deserialize', self.deserialize_raw) verify = ('Verify inputs', self.do_verify_inputs) self.advertised_actions['raw_transaction'] = [deserialize, verify] def create_layout(self): form = QFormLayout() self.raw_tx_edit = QPlainTextEdit() self.raw_tx_edit.setFont(monospace_font) self.raw_tx_edit.setContextMenuPolicy(Qt.CustomContextMenu) self.raw_tx_edit.customContextMenuRequested.connect(self.context_menu) self.raw_tx_edit.textChanged.connect(self.check_raw_tx) self.raw_tx_invalid = QLabel('Cannot parse transaction.') self.raw_tx_invalid.setProperty('hasError', True) self.tabs = tabs = QTabWidget() tabs.addTab(self.create_deserialize_tab(), 'Deserialize') tabs.addTab(self.create_verify_tab(), 'Verify') tabs.setTabToolTip(0, 'View the transaction in human-readable form') tabs.setTabToolTip(1, 'Download previous transactions and verify inputs') form.addRow('Raw Tx:', self.raw_tx_edit) form.addRow(self.raw_tx_invalid) form.addRow(tabs) return form def create_deserialize_tab(self): form = QFormLayout() self.deserialize_button = QPushButton('Deserialize') self.deserialize_button.clicked.connect(self.deserialize) btn_hbox = floated_buttons([self.deserialize_button]) self.tx_widget = TxWidget() self.tx_widget.inputs_tree.view.customContextMenuRequested.disconnect(self.tx_widget.inputs_tree.customContextMenu) self.tx_widget.inputs_tree.view.customContextMenuRequested.connect(self.inputs_context_menu) form.addRow(self.tx_widget) w = QWidget() w.setLayout(form) return w def create_verify_tab(self): form = QFormLayout() form.setRowWrapPolicy(QFormLayout.WrapLongRows) self.inputs_box = QSpinBox() self.verify_button = QPushButton('Verify') self.verify_button.clicked.connect(self.verify_input) self.verify_all_button = QPushButton('Verify All') self.verify_all_button.clicked.connect(self.verify_inputs) self.result_edit = QLineEdit() self.result_edit.setReadOnly(True) self.inputs_table = InputStatusTable() form.addRow('Verify Input:', floated_buttons([self.inputs_box, self.verify_button])) form.addRow(floated_buttons([self.verify_all_button])) form.addRow('Result:', self.result_edit) form.addRow(self.inputs_table) w = QWidget() w.setLayout(form) return w def context_menu(self, position): menu = QMenu() if self.tx: txt = str(self.raw_tx_edit.toPlainText()) self.handler.add_plugin_actions(self, menu, 'raw_transaction', txt) menu.exec_(self.mapToGlobal(position)) def inputs_context_menu(self, position): inputs = self.tx_widget.inputs_tree def inputs_context_verify(): self.tabs.setCurrentIndex(1) item = inputs.model.itemFromIndex(inputs.view.selectedIndexes()[0]) row = item.row() self.do_verify_input(self.tx, row) menu = QMenu() if self.tx: menu = inputs.context_menu() menu.addAction('Verify script', inputs_context_verify) menu.exec_(inputs.view.viewport().mapToGlobal(position)) def clear(self): self.result_edit.clear() self.tx_widget.clear() self.inputs_table.clear() def check_raw_tx(self): txt = str(self.raw_tx_edit.toPlainText()) tx = None valid = True try: tx = Transaction.deserialize(txt.decode('hex')) except Exception: valid = False self.tx = tx self.deserialize_button.setEnabled(valid) self.inputs_box.setEnabled(valid) self.verify_button.setEnabled(valid) self.verify_all_button.setEnabled(valid) self.tx_widget.setEnabled(valid) self.clear() if valid: self.raw_tx_invalid.hide() self.inputs_box.setRange(0, len(tx.vin) - 1) self.deserialize() elif txt: self.raw_tx_invalid.show() else: self.raw_tx_invalid.hide() def deserialize_raw(self, txt): """Deserialize a raw transaction.""" self.needsFocus.emit() self.raw_tx_edit.setPlainText(txt) self.deserialize() def deserialize(self): self.clear() self.tx_widget.set_tx(self.tx) self.inputs_table.set_tx(self.tx) self.status_message('Deserialized transaction {}'.format(bitcoin.core.b2lx(self.tx.GetHash()))) def do_verify_input(self, tx, in_idx): raw_prev_tx = None tx_in = tx.vin[in_idx] txid = b2lx(tx_in.prevout.hash) prev_out_n = tx_in.prevout.n try: raw_prev_tx = self.handler.download_blockchain_data('raw_transaction', txid) except Exception as e: self.status_message(str(e), True) return False try: prev_tx = Transaction.deserialize(raw_prev_tx.decode('hex')) result = bitcoin.core.scripteval.VerifyScript(tx_in.scriptSig, prev_tx.vout[prev_out_n].scriptPubKey, tx, in_idx) self.result_edit.setText('Successfully verified input {}'.format(in_idx)) self.inputs_table.set_verified(in_idx, True) except Exception as e: self.result_edit.setText(str(e)) self.inputs_table.set_verified(in_idx, False) self.status_message(str(e), True) return False return True def do_verify_inputs(self, txt): self.needsFocus.emit() self.raw_tx_edit.setPlainText(txt) tx = Transaction.deserialize(txt.decode('hex')) failed_inputs = [] self.result_edit.setText('Verifying...') for i in range(len(tx.vin)): if not self.do_verify_input(tx, i): failed_inputs.append(i) result = 'Successfully verified all inputs.' ret_val = True if failed_inputs: result = 'Failed to verify inputs: {}'.format(failed_inputs) ret_val = False if len(tx.vin) == 0: result = 'Transaction has no inputs.' self.result_edit.setText(result) return ret_val def verify_input(self): tx = None try: txt = str(self.raw_tx_edit.toPlainText()) tx = Transaction.deserialize(txt.decode('hex')) except Exception: self.status_message('Could not deserialize transaction.', True) return in_idx = self.inputs_box.value() if in_idx >= len(tx.vin): self.status_message('Input {} does not exist.'.format(in_idx), True) return self.do_verify_input(tx, in_idx) def verify_inputs(self): txt = str(self.raw_tx_edit.toPlainText()) self.do_verify_inputs(txt) def on_option_changed(self, key): if key == 'amount_format': self.needsUpdate.emit() def refresh_data(self): if self.tx: self.deserialize()
class TxBuilder(BaseDock): def init_metadata(self): self.tool_name = 'Transaction Builder' self.description = 'Transaction Builder helps you create transactions.' def init_data(self): self.tx = None def create_layout(self): vbox = QVBoxLayout() tabs = QTabWidget() tabs.addTab(self.create_version_locktime_tab(), '&Version/Locktime') tabs.addTab(self.create_inputs_tab(), '&Inputs') tabs.addTab(self.create_outputs_tab(), '&Outputs') tabs.addTab(self.create_review_tab(), '&Review') vbox.addWidget(tabs) return vbox def create_version_locktime_tab(self): form = QFormLayout() self.version_edit = AmountEdit() self.version_edit.setText('1') self.locktime_edit = AmountEdit() self.locktime_edit.setText('0') version_desc = QLabel('A transaction\'s version determines how it is interpreted.\n\nBitcoin transactions are currently version 1.') locktime_desc = QLabel('A transaction\'s locktime defines the earliest time or block that it may be added to the blockchain.\n\nLocktime only applies if it\'s non-zero and at least one input has a Sequence that\'s not the maximum possible value.') for i in [version_desc, locktime_desc]: i.setWordWrap(True) for i in [self.version_edit, self.locktime_edit]: i.setFont(monospace_font) form.addRow(version_desc) form.addRow('Version:', self.version_edit) form.addRow(Separator()) form.addRow(locktime_desc) form.addRow('Locktime:', self.locktime_edit) w = QWidget() w.setLayout(form) return w def create_inputs_tab(self): form = QFormLayout() self.inputs_tree = InputsTree() input_prev_tx = QLineEdit() input_prev_tx.setToolTip('Transaction ID of the tx with the output being spent') input_prev_vout = AmountEdit() input_prev_vout.setToolTip('Output index of the previous transaction') input_script = QTextEdit() input_script.setToolTip('Script that will be put on the stack before the previous output\'s script.') input_sequence = AmountEdit() input_sequence.setText('4294967295') maxify_input_sequence = QPushButton('Max') maxify_input_sequence.clicked.connect(lambda: input_sequence.setText('0xffffffff')) rm_input_edit = QSpinBox() rm_input_edit.setRange(0, 0) rm_input_button = QPushButton('Remove input') def add_input(): try: outpoint = COutPoint(lx(str(input_prev_tx.text())), input_prev_vout.get_amount()) in_script = Script.from_human(str(input_script.toPlainText())) new_input = CTxIn(outpoint, in_script.get_hex().decode('hex'), input_sequence.get_amount()) except Exception as e: self.status_message(str(e), True) return else: self.inputs_tree.add_input(new_input) rm_input_edit.setRange(0, len(self.inputs_tree.get_inputs()) - 1) def rm_input(): in_num = rm_input_edit.value() self.inputs_tree.model.takeRow(in_num) rm_input_edit.setRange(0, len(self.inputs_tree.get_inputs()) - 1) add_input_button = QPushButton('Add input') add_input_button.setToolTip('Add the above input') add_input_button.clicked.connect(add_input) rm_input_button.clicked.connect(rm_input) for i in [input_prev_tx, input_prev_vout, input_script, input_sequence]: i.setFont(monospace_font) form.addRow(self.inputs_tree) form.addRow(Separator()) form.addRow('Previous Transaction:', input_prev_tx) form.addRow('Previous Tx Output:', input_prev_vout) form.addRow('Input script:', input_script) seq_desc = QLabel('Sequence is mostly deprecated.\nIf an input has a sequence that\'s not the maximum value, the transaction\'s locktime will apply.') seq_desc.setWordWrap(True) form.addRow(seq_desc) form.addRow('Sequence:', HBox(input_sequence, maxify_input_sequence)) form.addRow(Separator()) form.addRow(floated_buttons([add_input_button])) form.addRow('Remove input:', HBox(rm_input_edit, rm_input_button)) w = QWidget() w.setLayout(form) return w def create_outputs_tab(self): form = QFormLayout() self.outputs_tree = OutputsTree() output_value = QLineEdit() output_script = QTextEdit() output_script.setToolTip('Script that will be put on the stack after the input that spends it.') rm_output_edit = QSpinBox() rm_output_edit.setRange(0, 0) rm_output_button = QPushButton('Remove output') def add_output(): try: val_str = str(output_value.text()) value = 0 if '.' in val_str: value = int(float(val_str) * pow(10, 8)) else: value = int(val_str) out_script = Script.from_human(str(output_script.toPlainText())) new_output = CTxOut(value, out_script.get_hex().decode('hex')) except Exception as e: self.status_message(str(e), True) return else: self.outputs_tree.add_output(new_output) rm_output_edit.setRange(0, len(self.outputs_tree.get_outputs()) - 1) def rm_output(): out_n = rm_output_edit.value() self.outputs_tree.model.takeRow(out_n) rm_output_edit.setRange(0, len(self.outputs_tree.get_outputs()) - 1) add_output_button = QPushButton('Add output') add_output_button.setToolTip('Add the above output') add_output_button.clicked.connect(add_output) rm_output_button.clicked.connect(rm_output) value_desc = QLabel('Include a decimal point if this value is not in satoshis.') value_desc.setWordWrap(True) for i in [output_value, output_script]: i.setFont(monospace_font) form.addRow(self.outputs_tree) form.addRow(Separator()) form.addRow(value_desc) form.addRow('Value:', output_value) form.addRow('Output script:', output_script) form.addRow(Separator()) form.addRow(floated_buttons([add_output_button])) form.addRow('Remove output:', HBox(rm_output_edit, rm_output_button)) w = QWidget() w.setLayout(form) return w def create_review_tab(self): form = QFormLayout() self.raw_tx = QTextEdit() self.raw_tx.setReadOnly(True) self.tx_widget = TxWidget() build_button = QPushButton('Build transaction') build_button.setToolTip('Build a tx from the data in the previous tabs') build_button.clicked.connect(self.build_transaction) form.addRow('Raw Tx:', self.raw_tx) form.addRow(self.tx_widget) form.addRow(floated_buttons([build_button])) w = QWidget() w.setLayout(form) return w def build_transaction(self): self.tx_widget.clear() self.tx = tx = CMutableTransaction() tx.nVersion = self.version_edit.get_amount() for i in self.inputs_tree.get_inputs(): tx.vin.append(i) for o in self.outputs_tree.get_outputs(): tx.vout.append(o) tx.nLockTime = self.locktime_edit.get_amount() self.raw_tx.setText(bitcoin.core.b2x(tx.serialize())) self.tx_widget.set_tx(tx) def on_option_changed(self, key): if key == 'amount_format': self.needsUpdate.emit() def refresh_data(self): self.build_transaction() self.outputs_tree.amount_format_changed()