def create_version_locktime_tab(self): form = QFormLayout() self.version_edit = AmountEdit() self.version_edit.setText('1') self.version_edit.setWhatsThis( 'Use this field to specify the version of your transaction. In Bitcoin, transactions are currently version 1.' ) self.locktime_edit = AmountEdit() self.locktime_edit.setText('0') self.locktime_edit.setWhatsThis( 'Use this field to specify the locktime of your transaction. For most common transactions, locktime is zero.' ) 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_version_locktime_tab(self): form = QFormLayout() self.version_edit = AmountEdit() self.version_edit.setText('1') self.version_edit.setWhatsThis('Use this field to specify the version of your transaction. In Bitcoin, transactions are currently version 1.') self.locktime_edit = AmountEdit() self.locktime_edit.setText('0') self.locktime_edit.setWhatsThis('Use this field to specify the locktime of your transaction. For most common transactions, locktime is zero.') 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_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 __init__(self, main_window, tree, parent=None): super(InputsEditor, self).__init__(tree, parent) self.prev_tx = QLineEdit() self.prev_tx.setToolTip('Transaction ID of the tx with the output being spent') self.prev_tx.setWhatsThis('Use this field to specify the transaction that contains the output you\'re spending.') self.prev_vout = AmountEdit() self.prev_vout.setToolTip('Output index of the previous transaction') self.prev_vout.setWhatsThis('Use this field to specify the output you are spending of the previous transaction.') self.script = ScriptEditor(main_window) self.script.setToolTip('Script that will be put on the stack before the previous output\'s script.') self.script.setWhatsThis('Enter a script here. This script will be evaluated directly before the script of the output you are spending. Any values that are pushed onto the stack when this script finishes its execution are present when the output script is evaluated afterward.') self.sequence = AmountEdit() self.sequence.setText('4294967295') self.sequence.setWhatsThis('Use this field to specify the sequence value. It\'s likely that you should leave this as its default (maximum) value.') maxify_input_sequence = QPushButton('Max') maxify_input_sequence.clicked.connect(lambda: self.sequence.setText('0xffffffff')) maxify_input_sequence.setWhatsThis('This button will set the sequence to its default value.') for i in [self.prev_tx, self.prev_vout, self.script, self.sequence]: i.setFont(monospace_font) self.mapper.addMapping(self.prev_tx, 0) self.mapper.addMapping(self.prev_vout, 1, 'amount') self.mapper.addMapping(self.script, 2, 'humanText') self.mapper.addMapping(self.sequence, 3, 'amount') delete_button = QPushButton('Remove Input') delete_button.setToolTip('Remove this input from the transaction') delete_button.clicked.connect(self.do_delete) submit_button = QPushButton('Save') submit_button.setToolTip('Update input with the above data') submit_button.clicked.connect(self.do_submit) form = QFormLayout() form.setContentsMargins(0, 0, 0, 0) form.addRow('Previous Transaction: ', self.prev_tx) form.addRow('Previous Tx Output: ', self.prev_vout) form.addRow('Input script: ', self.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(self.sequence, maxify_input_sequence)) form.addRow(floated_buttons([delete_button, submit_button])) self.setLayout(form)
class InputsEditor(BaseEditor): def __init__(self, main_window, tree, parent=None): super(InputsEditor, self).__init__(tree, parent) self.prev_tx = QLineEdit() self.prev_tx.setToolTip("Transaction ID of the tx with the output being spent") self.prev_vout = AmountEdit() self.prev_vout.setToolTip("Output index of the previous transaction") self.script = ScriptEditor(main_window) self.script.setToolTip("Script that will be put on the stack before the previous output's script.") self.sequence = AmountEdit() self.sequence.setText("4294967295") maxify_input_sequence = QPushButton("Max") maxify_input_sequence.clicked.connect(lambda: self.sequence.setText("0xffffffff")) for i in [self.prev_tx, self.prev_vout, self.script, self.sequence]: i.setFont(monospace_font) self.mapper.addMapping(self.prev_tx, 0) self.mapper.addMapping(self.prev_vout, 1, "amount") self.mapper.addMapping(self.script, 2, "humanText") self.mapper.addMapping(self.sequence, 3, "amount") delete_button = QPushButton("Remove Input") delete_button.setToolTip("Remove this input from the transaction") delete_button.clicked.connect(self.do_delete) submit_button = QPushButton("Save") submit_button.setToolTip("Update input with the above data") submit_button.clicked.connect(self.do_submit) form = QFormLayout() form.setContentsMargins(0, 0, 0, 0) form.addRow("Previous Transaction: ", self.prev_tx) form.addRow("Previous Tx Output: ", self.prev_vout) form.addRow("Input script: ", self.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(self.sequence, maxify_input_sequence)) form.addRow(floated_buttons([delete_button, submit_button])) self.setLayout(form)
class InputsEditor(BaseEditor): def __init__(self, main_window, tree, parent=None): super(InputsEditor, self).__init__(tree, parent) self.prev_tx = QLineEdit() self.prev_tx.setToolTip('Transaction ID of the tx with the output being spent') self.prev_vout = AmountEdit() self.prev_vout.setToolTip('Output index of the previous transaction') self.script = ScriptEditor(main_window) self.script.setToolTip('Script that will be put on the stack before the previous output\'s script.') self.sequence = AmountEdit() self.sequence.setText('4294967295') maxify_input_sequence = QPushButton('Max') maxify_input_sequence.clicked.connect(lambda: self.sequence.setText('0xffffffff')) for i in [self.prev_tx, self.prev_vout, self.script, self.sequence]: i.setFont(monospace_font) self.mapper.addMapping(self.prev_tx, 0) self.mapper.addMapping(self.prev_vout, 1, 'amount') self.mapper.addMapping(self.script, 2, 'humanText') self.mapper.addMapping(self.sequence, 3, 'amount') delete_button = QPushButton('Remove Input') delete_button.setToolTip('Remove this input from the transaction') delete_button.clicked.connect(self.do_delete) submit_button = QPushButton('Save') submit_button.setToolTip('Update input with the above data') submit_button.clicked.connect(self.do_submit) form = QFormLayout() form.setContentsMargins(0, 0, 0, 0) form.addRow('Previous Transaction: ', self.prev_tx) form.addRow('Previous Tx Output: ', self.prev_vout) form.addRow('Input script: ', self.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(self.sequence, maxify_input_sequence)) form.addRow(floated_buttons([delete_button, submit_button])) self.setLayout(form)
def create_block_tab(self): form = QFormLayout() desc = QLabel(''.join(['For most purposes, this tab can be ignored.\n\n' 'You can simulate the block that contains the script you\'re testing. ', 'This allows you to use certain opcodes like CHECKLOCKTIMEVERIFY, which require ', 'data about the block a transaction is in.'])) desc.setWordWrap(True) form.addRow(desc) self.block_height_edit = AmountEdit() self.block_height_edit.setToolTip('Height of the block your script is in') self.block_height_edit.setWhatsThis('Use this to simulate the height of the block that your script is in.') self.block_time_edit = AmountEdit() self.block_time_edit.setToolTip('Timestamp of the block your script is in') self.block_time_edit.setWhatsThis('Use this to simulate the timestamp of the block that your script is in.') form.addRow('Block height:', self.block_height_edit) form.addRow('Block time:', self.block_time_edit) w = QWidget() w.setLayout(form) return w
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)
class StackEval(BaseDock): tool_name = 'Stack Evaluator' description = '\n'.join([ 'Stack Evaluator steps through scripts, showing you what\'s happening as it happens.', '<b>Please read this warning from the source of python-bitcoinlib, which Stack Evaluator uses to evaluate scripts:</b>', '"Be warned that there are highly likely to be consensus bugs in this code; it is unlikely to match Satoshi Bitcoin exactly. Think carefully before using this module."' ]) is_large = True category = Category.Script def __init__(self, handler): super(StackEval, self).__init__(handler) self.widget().setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) @augmenter def item_actions(self, *args): return ItemAction(self.tool_name, 'Transaction', 'Set as spending transaction', self.set_spending_item) def init_data(self): self.tx = None self.inIdx = 0 self.execution = ScriptExecution() def reset(self): self.tx_script.clear() self.clear_execution() def clear_execution(self): self.execution_widget.clear() for i in [self.script_passed, self.script_verified]: i.setChecked(False) i.setProperty('hasSuccess', False) self.style().polish(i) def create_layout(self): vbox = QVBoxLayout() tabs = QTabWidget() tabs.addTab(self.create_main_tab(), 'Stack') tabs.addTab(self.create_tx_tab(), 'Transaction') tabs.addTab(self.create_block_tab(), 'Block') self.setFocusProxy(tabs) vbox.addWidget(tabs) return vbox def create_main_tab(self): self.execution_widget = ScriptExecutionWidget(self.execution) # For variable substitution self.execution_widget.model.plugin_handler = self.handler self.execution_delegate = ScriptExecutionDelegate() self.execution_widget.view.setItemDelegate(self.execution_delegate) # Raw script input. self.tx_script = QPlainTextEdit() self.tx_script.setWhatsThis('Enter a raw script here to evaluate it.') self.tx_script.setFont(monospace_font) self.tx_script.setTabChangesFocus(True) self.clear_button = QPushButton('Clear') self.clear_button.setToolTip('Clear the current script.') self.clear_button.clicked.connect(self.reset) self.do_button = QPushButton('&Evaluate') self.do_button.setToolTip('Evaluate the entire script.') self.do_button.clicked.connect(self.do_evaluate) btn_hbox = floated_buttons([self.clear_button, self.do_button], left=True) vbox = QVBoxLayout() vbox.addWidget(QLabel('Script:')) vbox.addWidget(self.tx_script) vbox.addLayout(btn_hbox) vbox.addWidget(self.execution_widget, stretch=1) self.next_button = QPushButton('Next') self.next_button.setToolTip('Step forward in script execution.') self.next_button.clicked.connect(self.execution_widget.select_next) self.prev_button = QPushButton('Previous') self.prev_button.setToolTip('Step backward in script execution.') self.prev_button.clicked.connect(self.execution_widget.select_prev) controls_hbox = floated_buttons([self.prev_button, self.next_button], left=True) vbox.addLayout(controls_hbox) self.script_passed = ReadOnlyCheckBox('Passed') self.script_passed.setToolTip('Whether the script passed') self.script_passed.setWhatsThis('This box is checked if the script finished with a nonzero top stack value.') self.script_verified = ReadOnlyCheckBox('Verified') self.script_verified.setToolTip('Whether the script was verified with an input script') self.script_verified.setWhatsThis('This box is checked if the script was verified with a transaction\'s input script.') pass_hbox = HBox(QLabel('Script: '), self.script_passed, self.script_verified) pass_hbox.addStretch(1) vbox.addLayout(pass_hbox) w = QWidget() w.setLayout(vbox) return w def create_tx_tab(self): form = QFormLayout() # Spending transaction self.tx_edit = QPlainTextEdit() self.tx_edit.setWhatsThis('Enter a serialized transaction here. If you have a raw transaction stored in the Variables tool, you can enter the variable name preceded by a "$", and the variable value will be substituted automatically.') self.tx_edit.setFont(monospace_font) self.tx_edit.textChanged.connect(self.set_tx) self.handler.substitute_variables(self.tx_edit) self.tx_edit.setTabChangesFocus(True) # Input with scriptSig to include self.input_idx = QSpinBox() self.input_idx.setEnabled(False) self.input_idx.valueChanged.connect(self.set_input_index) self.input_idx.setToolTip('Input in the containing transaction with the relevant scriptSig.') self.input_idx.setWhatsThis('Use this to specify the input you want to simulate.') in_idx_box = QHBoxLayout() in_idx_box.addWidget(QLabel('Input containing scriptSig:')) in_idx_box.addWidget(self.input_idx) in_idx_box.addStretch(1) desc = QLabel(' '.join(['You can specify the transaction that contains the script you\'re testing.', 'This allows you to evaluate whether an input spends successfully.'])) desc.setWordWrap(True) form.addRow(desc) form.addRow('Raw Transaction:', self.tx_edit) form.addRow('Spending Input:', self.input_idx) w = QWidget() w.setLayout(form) return w def create_block_tab(self): form = QFormLayout() desc = QLabel(''.join(['For most purposes, this tab can be ignored.\n\n' 'You can simulate the block that contains the script you\'re testing. ', 'This allows you to use certain opcodes like CHECKLOCKTIMEVERIFY, which require ', 'data about the block a transaction is in.'])) desc.setWordWrap(True) form.addRow(desc) self.block_height_edit = AmountEdit() self.block_height_edit.setToolTip('Height of the block your script is in') self.block_height_edit.setWhatsThis('Use this to simulate the height of the block that your script is in.') self.block_time_edit = AmountEdit() self.block_time_edit.setToolTip('Timestamp of the block your script is in') self.block_time_edit.setWhatsThis('Use this to simulate the timestamp of the block that your script is in.') form.addRow('Block height:', self.block_height_edit) form.addRow('Block time:', self.block_time_edit) w = QWidget() w.setLayout(form) return w def set_spending_item(self, item): """Called from other tools to set the spending transaction.""" self.needsFocus.emit() self.tx_edit.setPlainText(item.raw()) def set_tx(self): """Set the spending transaction and (en|dis)able the input index box.""" txt = str(self.tx_edit.toPlainText()) # Variable substition if txt.startswith('$'): return try: assert txt self.tx = Transaction.deserialize(txt.decode('hex')) self.tx_edit.setToolTip(''.join(['Tx ID: ', bitcoin.core.b2lx(self.tx.GetHash())])) self.input_idx.setRange(0, len(self.tx.vin) - 1) self.input_idx.setEnabled(True) except Exception: self.tx = None self.tx_edit.setToolTip('') self.input_idx.setEnabled(False) def set_input_index(self, idx): self.inIdx = idx def evaluate_script(self, s): """Called by Plugin Handler to evaluate the current script.""" self.needsFocus.emit() self.tx_script.setPlainText(s) self.do_button.animateClick() def do_evaluate(self): self.clear_execution() try: scr = Script(str(self.tx_script.toPlainText()).decode('hex')) except Exception as e: self.error('Error decoding script: %s' % str(e)) return exec_data = None if not self.block_height_edit.property('hasError').toBool() and not self.block_time_edit.property('hasError').toBool(): exec_data = ExecutionData(self.block_height_edit.get_amount(), self.block_time_edit.get_amount()) self.execution_widget.evaluate(scr, self.tx, self.inIdx, execution_data=exec_data) passed = True if self.execution_widget.execution.script_passed else False verified = self.execution_widget.execution.script_verified self.script_passed.setChecked(passed) self.script_verified.setChecked(verified) for widget in [self.script_passed, self.script_verified]: widget.setProperty('hasSuccess', widget.isChecked()) self.style().polish(widget)
def __init__(self, main_window, tree, parent=None): super(InputsEditor, self).__init__(tree, parent) self.prev_tx = QLineEdit() self.prev_tx.setToolTip( 'Transaction ID of the tx with the output being spent') self.prev_tx.setWhatsThis( 'Use this field to specify the transaction that contains the output you\'re spending.' ) self.prev_vout = AmountEdit() self.prev_vout.setToolTip('Output index of the previous transaction') self.prev_vout.setWhatsThis( 'Use this field to specify the output you are spending of the previous transaction.' ) self.script = ScriptEditor(main_window) self.script.setToolTip( 'Script that will be put on the stack before the previous output\'s script.' ) self.script.setWhatsThis( 'Enter a script here. This script will be evaluated directly before the script of the output you are spending. Any values that are pushed onto the stack when this script finishes its execution are present when the output script is evaluated afterward.' ) self.sequence = AmountEdit() self.sequence.setText('4294967295') self.sequence.setWhatsThis( 'Use this field to specify the sequence value. It\'s likely that you should leave this as its default (maximum) value.' ) maxify_input_sequence = QPushButton('Max') maxify_input_sequence.clicked.connect( lambda: self.sequence.setText('0xffffffff')) maxify_input_sequence.setWhatsThis( 'This button will set the sequence to its default value.') for i in [self.prev_tx, self.prev_vout, self.script, self.sequence]: i.setFont(monospace_font) self.mapper.addMapping(self.prev_tx, 0) self.mapper.addMapping(self.prev_vout, 1, 'amount') self.mapper.addMapping(self.script, 2, 'humanText') self.mapper.addMapping(self.sequence, 3, 'amount') delete_button = QPushButton('Remove Input') delete_button.setToolTip('Remove this input from the transaction') delete_button.clicked.connect(self.do_delete) submit_button = QPushButton('Save') submit_button.setToolTip('Update input with the above data') submit_button.clicked.connect(self.do_submit) form = QFormLayout() form.setContentsMargins(0, 0, 0, 0) form.addRow('Previous Transaction: ', self.prev_tx) form.addRow('Previous Tx Output: ', self.prev_vout) form.addRow('Input script: ', self.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(self.sequence, maxify_input_sequence)) form.addRow(floated_buttons([delete_button, submit_button])) self.setLayout(form)
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) @augmenter def item_actions(self, *args): return [ ItemAction(self.tool_name, 'Transaction', 'Edit', self.deserialize_item) ] 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') tabs.addTab(self.create_sign_tab(), 'Sig&n') self.setFocusProxy(self.tabs) 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' or str( tabs.tabText(i)) == 'Sig&n': 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, 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.version_edit.setWhatsThis( 'Use this field to specify the version of your transaction. In Bitcoin, transactions are currently version 1.' ) self.locktime_edit = AmountEdit() self.locktime_edit.setText('0') self.locktime_edit.setWhatsThis( 'Use this field to specify the locktime of your transaction. For most common transactions, locktime is zero.' ) 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() self.inputs_tree.view.setWhatsThis( 'The inputs of your transaction are displayed here.') self.inputs_editor = InputsEditor(self.handler.gui, self.inputs_tree) self.inputs_editor.setEnabled(False) def update_enabled_widgets(): num_inputs = len(self.inputs_tree.get_inputs()) self.inputs_editor.setEnabled(num_inputs > 0) def add_input(): outpoint = CMutableOutPoint(n=0) new_input = CMutableTxIn(prevout=outpoint) self.inputs_tree.add_input(new_input) update_enabled_widgets() if len(self.inputs_tree.get_inputs()) > 0: self.inputs_tree.view.selectRow( self.inputs_tree.model.rowCount() - 1) update_enabled_widgets() add_input_button = QPushButton('New input') add_input_button.setToolTip('Add a new input') add_input_button.setWhatsThis( 'Clicking this button will add a new input to your transaction.') add_input_button.clicked.connect(add_input) form.addRow(self.inputs_tree) form.addRow(Separator()) form.addRow(self.inputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_input_button])) w = QWidget() w.setLayout(form) return w def create_outputs_tab(self): form = QFormLayout() self.outputs_tree = OutputsTree() self.outputs_tree.view.setWhatsThis( 'The outputs of your transaction are displayed here.') self.outputs_editor = OutputsEditor(self.handler.gui, self.outputs_tree) self.outputs_editor.setEnabled(False) def update_enabled_widgets(): num_outputs = len(self.outputs_tree.get_outputs()) self.outputs_editor.setEnabled(num_outputs > 0) def add_output(): new_output = CMutableTxOut(0) self.outputs_tree.add_output(new_output) update_enabled_widgets() if len(self.outputs_tree.get_outputs()) > 0: self.outputs_tree.view.selectRow( self.outputs_tree.model.rowCount() - 1) update_enabled_widgets() add_output_button = QPushButton('New output') add_output_button.setToolTip('Add a new output') add_output_button.setWhatsThis( 'Clicking this button will add a new output to your transaction.') add_output_button.clicked.connect(add_output) form.addRow(self.outputs_tree) form.addRow(Separator()) form.addRow(self.outputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_output_button])) w = QWidget() w.setLayout(form) return w def create_review_tab(self): form = QFormLayout() self.raw_tx = QTextEdit() self.raw_tx.setWhatsThis( 'The transaction you build is displayed here.') self.raw_tx.setReadOnly(True) self.tx_widget = TxWidget() form.addRow('Raw Tx:', self.raw_tx) form.addRow(self.tx_widget) 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 create_sign_tab(self): self.sighash_widget = SigHashWidget(self) return self.sighash_widget def deserialize_item(self, item): self.deserialize_raw(item.raw()) def deserialize_raw(self, rawtx): """Update editor widgets with rawtx's data.""" self.needsFocus.emit() try: tx = Transaction.deserialize(x(rawtx)) except Exception: return else: self.version_edit.set_amount(tx.nVersion) self.inputs_tree.model.set_tx(tx) self.outputs_tree.model.set_tx(tx) self.locktime_edit.set_amount(tx.nLockTime) for name, w in self.tx_field_widgets: if name in ['nVersion', 'vin', 'vout', 'nLockTime']: continue try: value = getattr(tx, name) except AttributeError: continue if isinstance(w, AmountEdit): w.set_amount(value) else: w.setText(str(value)) self.build_transaction() def build_transaction(self): self.tx_widget.clear() self.sighash_widget.clear() self.tx = tx = Transaction() tx.nVersion = self.version_edit.get_amount() tx.vin = self.inputs_tree.get_inputs() tx.vout = self.outputs_tree.get_outputs() 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) self.sighash_widget.set_tx(tx) def on_option_changed(self, key): if key == '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()
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) @augmenter def item_actions(self, *args): return [ItemAction(self.tool_name, 'Transaction', 'Edit', self.deserialize_item)] 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.setFocusProxy(self.tabs) 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, 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.version_edit.setWhatsThis('Use this field to specify the version of your transaction. In Bitcoin, transactions are currently version 1.') self.locktime_edit = AmountEdit() self.locktime_edit.setText('0') self.locktime_edit.setWhatsThis('Use this field to specify the locktime of your transaction. For most common transactions, locktime is zero.') 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() self.inputs_tree.view.setWhatsThis('The inputs of your transaction are displayed here.') self.inputs_editor = InputsEditor(self.handler.gui, self.inputs_tree) self.inputs_editor.setEnabled(False) def update_enabled_widgets(): num_inputs = len(self.inputs_tree.get_inputs()) self.inputs_editor.setEnabled(num_inputs > 0) def add_input(): outpoint = CMutableOutPoint(n=0) new_input = CMutableTxIn(prevout=outpoint) self.inputs_tree.add_input(new_input) update_enabled_widgets() if len(self.inputs_tree.get_inputs()) > 0: self.inputs_tree.view.selectRow(self.inputs_tree.model.rowCount() - 1) update_enabled_widgets() add_input_button = QPushButton('New input') add_input_button.setToolTip('Add a new input') add_input_button.setWhatsThis('Clicking this button will add a new input to your transaction.') add_input_button.clicked.connect(add_input) form.addRow(self.inputs_tree) form.addRow(Separator()) form.addRow(self.inputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_input_button])) w = QWidget() w.setLayout(form) return w def create_outputs_tab(self): form = QFormLayout() self.outputs_tree = OutputsTree() self.outputs_tree.view.setWhatsThis('The outputs of your transaction are displayed here.') self.outputs_editor = OutputsEditor(self.handler.gui, self.outputs_tree) self.outputs_editor.setEnabled(False) def update_enabled_widgets(): num_outputs = len(self.outputs_tree.get_outputs()) self.outputs_editor.setEnabled(num_outputs > 0) def add_output(): new_output = CMutableTxOut(0) self.outputs_tree.add_output(new_output) update_enabled_widgets() if len(self.outputs_tree.get_outputs()) > 0: self.outputs_tree.view.selectRow(self.outputs_tree.model.rowCount() - 1) update_enabled_widgets() add_output_button = QPushButton('New output') add_output_button.setToolTip('Add a new output') add_output_button.setWhatsThis('Clicking this button will add a new output to your transaction.') add_output_button.clicked.connect(add_output) form.addRow(self.outputs_tree) form.addRow(Separator()) form.addRow(self.outputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_output_button])) w = QWidget() w.setLayout(form) return w def create_review_tab(self): form = QFormLayout() self.raw_tx = QTextEdit() self.raw_tx.setWhatsThis('The transaction you build is displayed here.') self.raw_tx.setReadOnly(True) self.tx_widget = TxWidget() form.addRow('Raw Tx:', self.raw_tx) form.addRow(self.tx_widget) 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 deserialize_item(self, item): self.deserialize_raw(item.raw()) def deserialize_raw(self, rawtx): """Update editor widgets with rawtx's data.""" self.needsFocus.emit() try: tx = Transaction.deserialize(x(rawtx)) except Exception: return else: self.version_edit.set_amount(tx.nVersion) self.inputs_tree.model.set_tx(tx) self.outputs_tree.model.set_tx(tx) self.locktime_edit.set_amount(tx.nLockTime) for name, w in self.tx_field_widgets: if name in ['nVersion', 'vin', 'vout', 'nLockTime']: continue try: value = getattr(tx, name) except AttributeError: continue if isinstance(w, AmountEdit): w.set_amount(value) else: w.setText(str(value)) self.build_transaction() def build_transaction(self): self.tx_widget.clear() self.tx = tx = Transaction() tx.nVersion = self.version_edit.get_amount() tx.vin = self.inputs_tree.get_inputs() tx.vout = self.outputs_tree.get_outputs() 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 ['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()
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 init_actions(self): self.advertised_actions[RAW_TX] = [("Edit", self.deserialize_raw)] 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.setFocusProxy(self.tabs) 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_TX, 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() self.inputs_editor = InputsEditor(self.handler.gui, self.inputs_tree) self.inputs_editor.setEnabled(False) def update_enabled_widgets(): num_inputs = len(self.inputs_tree.get_inputs()) self.inputs_editor.setEnabled(num_inputs > 0) def add_input(): outpoint = CMutableOutPoint(n=0) new_input = CMutableTxIn(prevout=outpoint) self.inputs_tree.add_input(new_input) update_enabled_widgets() if len(self.inputs_tree.get_inputs()) > 0: self.inputs_tree.view.selectRow(self.inputs_tree.model.rowCount() - 1) update_enabled_widgets() add_input_button = QPushButton("New input") add_input_button.setToolTip("Add a new input") add_input_button.clicked.connect(add_input) form.addRow(self.inputs_tree) form.addRow(Separator()) form.addRow(self.inputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_input_button])) w = QWidget() w.setLayout(form) return w def create_outputs_tab(self): form = QFormLayout() self.outputs_tree = OutputsTree() self.outputs_editor = OutputsEditor(self.handler.gui, self.outputs_tree) self.outputs_editor.setEnabled(False) def update_enabled_widgets(): num_outputs = len(self.outputs_tree.get_outputs()) self.outputs_editor.setEnabled(num_outputs > 0) def add_output(): new_output = CMutableTxOut(0) self.outputs_tree.add_output(new_output) update_enabled_widgets() if len(self.outputs_tree.get_outputs()) > 0: self.outputs_tree.view.selectRow(self.outputs_tree.model.rowCount() - 1) update_enabled_widgets() add_output_button = QPushButton("New output") add_output_button.setToolTip("Add a new output") add_output_button.clicked.connect(add_output) form.addRow(self.outputs_tree) form.addRow(Separator()) form.addRow(self.outputs_editor) form.addRow(Separator()) form.addRow(floated_buttons([add_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() form.addRow("Raw Tx:", self.raw_tx) form.addRow(self.tx_widget) 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 deserialize_raw(self, rawtx): """Update editor widgets with rawtx's data.""" self.needsFocus.emit() try: tx = Transaction.deserialize(x(rawtx)) except Exception: return else: self.version_edit.set_amount(tx.nVersion) self.inputs_tree.model.set_tx(tx) self.outputs_tree.model.set_tx(tx) self.locktime_edit.set_amount(tx.nLockTime) for name, w in self.tx_field_widgets: if name in ["nVersion", "vin", "vout", "nLockTime"]: continue value = getattr(tx, name) if isinstance(w, AmountEdit): w.set_amount(value) else: w.setText(str(value)) self.build_transaction() def build_transaction(self): self.tx_widget.clear() self.tx = tx = Transaction() tx.nVersion = self.version_edit.get_amount() tx.vin = self.inputs_tree.get_inputs() tx.vout = self.outputs_tree.get_outputs() 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 ["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()
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
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 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()