bitcoinkernel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Represents the active blockchain.
5
+ # This is a view into the chainstate manager's active chain.
6
+ #
7
+ # Note: This class does not inherit from FFI::AutoPointer because:
8
+ # - There is no btck_chain_destroy function in the library
9
+ # - The pointer is not owned; it points to internal data of ChainstateManager
10
+ # - The lifetime depends on the parent ChainstateManager
11
+ class Chain
12
+ # @param [FFI::Pointer] ptr Pointer to btck_Chain
13
+ # @param [Boolean] owned Whether this object owns the pointer.
14
+ # Chain pointers from ChainstateManager are NOT owned.
15
+ def initialize(ptr, owned: false)
16
+ @ptr = ptr
17
+ @owned = owned
18
+ end
19
+
20
+ # Get the height of the chain tip.
21
+ # @return [Integer]
22
+ def height
23
+ BitcoinKernel.btck_chain_get_height(@ptr)
24
+ end
25
+
26
+ # Get the block tree entry at the specified height.
27
+ # @param [Integer] block_height The height to query
28
+ # @return [BlockTreeEntry, nil] The block tree entry, or nil if height is out of bounds
29
+ def entry_at(block_height)
30
+ ptr = BitcoinKernel.btck_chain_get_by_height(@ptr, block_height)
31
+ return nil if ptr.null?
32
+ BlockTreeEntry.new(ptr, owned: false)
33
+ end
34
+
35
+ # Check if the chain contains a block tree entry.
36
+ # @param [BlockTreeEntry] entry The entry to check
37
+ # @return [Boolean]
38
+ def contains?(entry)
39
+ BitcoinKernel.btck_chain_contains(@ptr, entry.to_ptr) == 1
40
+ end
41
+
42
+ # Get the underlying pointer for FFI calls.
43
+ # @return [FFI::Pointer]
44
+ def to_ptr
45
+ @ptr
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Represents chain parameters for a specific network.
5
+ class ChainParameters < FFI::AutoPointer
6
+ # Create chain parameters for the specified network.
7
+ # @param [Integer] chain_type One of ChainType constants (MAINNET, TESTNET, etc.)
8
+ # @return [ChainParameters]
9
+ def self.create(chain_type = ChainType::MAINNET)
10
+ ptr = BitcoinKernel.btck_chain_parameters_create(chain_type)
11
+ raise Error, "Failed to create chain parameters" if ptr.null?
12
+ new(ptr)
13
+ end
14
+
15
+ # Create chain parameters for mainnet.
16
+ # @return [ChainParameters]
17
+ def self.mainnet
18
+ create(ChainType::MAINNET)
19
+ end
20
+
21
+ # Create chain parameters for testnet.
22
+ # @return [ChainParameters]
23
+ def self.testnet
24
+ create(ChainType::TESTNET)
25
+ end
26
+
27
+ # Create chain parameters for testnet4.
28
+ # @return [ChainParameters]
29
+ def self.testnet4
30
+ create(ChainType::TESTNET_4)
31
+ end
32
+
33
+ # Create chain parameters for signet.
34
+ # @return [ChainParameters]
35
+ def self.signet
36
+ create(ChainType::SIGNET)
37
+ end
38
+
39
+ # Create chain parameters for regtest.
40
+ # @return [ChainParameters]
41
+ def self.regtest
42
+ create(ChainType::REGTEST)
43
+ end
44
+
45
+ def self.release(ptr)
46
+ BitcoinKernel.btck_chain_parameters_destroy(ptr)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Manages chainstate and block validation.
5
+ class ChainstateManager < FFI::AutoPointer
6
+ # Create a chainstate manager.
7
+ # @param [ChainstateManagerOptions] options Options for the chainstate manager
8
+ # @return [ChainstateManager]
9
+ def self.create(options)
10
+ ptr = BitcoinKernel.btck_chainstate_manager_create(options)
11
+ raise Error, "Failed to create chainstate manager" if ptr.null?
12
+ new(ptr)
13
+ end
14
+
15
+ def self.release(ptr)
16
+ BitcoinKernel.btck_chainstate_manager_destroy(ptr)
17
+ end
18
+
19
+ # Process and validate a block.
20
+ # @param [Block] block The block to process
21
+ # @return [Boolean] true if processing was successful (not indicative of validity)
22
+ def process_block(block)
23
+ new_block_ptr = FFI::MemoryPointer.new(:int)
24
+ result = BitcoinKernel.btck_chainstate_manager_process_block(self, block, new_block_ptr)
25
+ result == 0
26
+ end
27
+
28
+ # Get the currently active chain.
29
+ # @return [Chain]
30
+ def active_chain
31
+ ptr = BitcoinKernel.btck_chainstate_manager_get_active_chain(self)
32
+ raise Error, "Failed to get active chain" if ptr.null?
33
+ Chain.new(ptr, owned: false)
34
+ end
35
+
36
+ # Get a block tree entry by its hash.
37
+ # @param [BlockHash] block_hash The block hash to look up
38
+ # @return [BlockTreeEntry, nil] The block tree entry, or nil if not found
39
+ def block_tree_entry_by_hash(block_hash)
40
+ ptr = BitcoinKernel.btck_chainstate_manager_get_block_tree_entry_by_hash(self, block_hash)
41
+ return nil if ptr.null?
42
+ BlockTreeEntry.new(ptr, owned: false)
43
+ end
44
+
45
+ # Read a block from disk by its block tree entry.
46
+ # @param [BlockTreeEntry] entry The block tree entry
47
+ # @return [Block]
48
+ def read_block(entry)
49
+ ptr = BitcoinKernel.btck_block_read(self, entry.to_ptr)
50
+ raise Error, "Failed to read block" if ptr.null?
51
+ Block.new(ptr)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Options for creating a ChainstateManager.
5
+ class ChainstateManagerOptions < FFI::AutoPointer
6
+ # Create chainstate manager options.
7
+ # @param [Context] context The kernel context
8
+ # @param [String] data_directory Path to the chainstate data directory
9
+ # @param [String] blocks_directory Path to the blocks directory
10
+ # @return [ChainstateManagerOptions]
11
+ def self.create(context:, data_directory:, blocks_directory:)
12
+ ptr = BitcoinKernel.btck_chainstate_manager_options_create(
13
+ context,
14
+ data_directory, data_directory.bytesize,
15
+ blocks_directory, blocks_directory.bytesize
16
+ )
17
+ raise Error, "Failed to create chainstate manager options" if ptr.null?
18
+ new(ptr)
19
+ end
20
+
21
+ def self.release(ptr)
22
+ BitcoinKernel.btck_chainstate_manager_options_destroy(ptr)
23
+ end
24
+
25
+ # Set the number of worker threads for validation.
26
+ # @param [Integer] num Number of worker threads (0-15)
27
+ # @return [self]
28
+ def set_worker_threads(num)
29
+ BitcoinKernel.btck_chainstate_manager_options_set_worker_threads_num(self, num)
30
+ self
31
+ end
32
+
33
+ # Set wipe database options for reindexing.
34
+ # @param [Boolean] wipe_block_tree_db Wipe block tree db (should only be true if wipe_chainstate_db is also true)
35
+ # @param [Boolean] wipe_chainstate_db Wipe chainstate db
36
+ # @return [Boolean] true if successful
37
+ def set_wipe_dbs(wipe_block_tree_db:, wipe_chainstate_db:)
38
+ result = BitcoinKernel.btck_chainstate_manager_options_set_wipe_dbs(
39
+ self,
40
+ wipe_block_tree_db ? 1 : 0,
41
+ wipe_chainstate_db ? 1 : 0
42
+ )
43
+ result == 0
44
+ end
45
+
46
+ # Set block tree db to be in memory.
47
+ # @param [Boolean] in_memory Whether to use in-memory database
48
+ # @return [self]
49
+ def set_block_tree_db_in_memory(in_memory = true)
50
+ BitcoinKernel.btck_chainstate_manager_options_update_block_tree_db_in_memory(self, in_memory ? 1 : 0)
51
+ self
52
+ end
53
+
54
+ # Set chainstate db to be in memory.
55
+ # @param [Boolean] in_memory Whether to use in-memory database
56
+ # @return [self]
57
+ def set_chainstate_db_in_memory(in_memory = true)
58
+ BitcoinKernel.btck_chainstate_manager_options_update_chainstate_db_in_memory(self, in_memory ? 1 : 0)
59
+ self
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Represents the kernel context.
5
+ class Context < FFI::AutoPointer
6
+ # Create a new context with the given options.
7
+ # @param [ContextOptions, nil] options Context options (nil for defaults)
8
+ # @return [Context]
9
+ def self.create(options = nil)
10
+ opts_ptr = options&.to_ptr
11
+ ptr = BitcoinKernel.btck_context_create(opts_ptr)
12
+ raise Error, "Failed to create context" if ptr.null?
13
+ new(ptr)
14
+ end
15
+
16
+ def self.release(ptr)
17
+ BitcoinKernel.btck_context_destroy(ptr)
18
+ end
19
+
20
+ # Interrupt the context.
21
+ # @return [Boolean] true if interrupt was successful
22
+ def interrupt
23
+ BitcoinKernel.btck_context_interrupt(self) == 0
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Options for creating a Context.
5
+ class ContextOptions < FFI::AutoPointer
6
+ # Create new context options.
7
+ # @return [ContextOptions]
8
+ def self.create
9
+ ptr = BitcoinKernel.btck_context_options_create
10
+ raise Error, "Failed to create context options" if ptr.null?
11
+ new(ptr)
12
+ end
13
+
14
+ def self.release(ptr)
15
+ BitcoinKernel.btck_context_options_destroy(ptr)
16
+ end
17
+
18
+ # Set chain parameters for this context.
19
+ # @param [ChainParameters] chain_params Chain parameters to use
20
+ # @return [self]
21
+ def set_chainparams(chain_params)
22
+ BitcoinKernel.btck_context_options_set_chainparams(self, chain_params)
23
+ self
24
+ end
25
+
26
+ # Set validation interface callbacks for this context.
27
+ # @param [ValidationInterface] validation_interface The validation interface with callbacks
28
+ # @return [self]
29
+ def set_validation_interface(validation_interface)
30
+ # Store reference to prevent garbage collection of callbacks
31
+ @validation_interface = validation_interface
32
+ callbacks = validation_interface.to_ffi_callbacks
33
+ BitcoinKernel.btck_context_options_set_validation_interface(self, callbacks)
34
+ self
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Logging configuration for Bitcoin Kernel.
5
+ module Logging
6
+ # Logging options struct for FFI
7
+ class Options < FFI::Struct
8
+ layout :log_timestamps, :int,
9
+ :log_time_micros, :int,
10
+ :log_threadnames, :int,
11
+ :log_sourcelocations, :int,
12
+ :always_print_category_levels, :int
13
+ end
14
+
15
+ # Log categories
16
+ module Category
17
+ ALL = 0
18
+ BENCH = 1
19
+ BLOCKSTORAGE = 2
20
+ COINDB = 3
21
+ LEVELDB = 4
22
+ MEMPOOL = 5
23
+ PRUNE = 6
24
+ RAND = 7
25
+ REINDEX = 8
26
+ VALIDATION = 9
27
+ KERNEL = 10
28
+ end
29
+
30
+ # Log levels
31
+ module Level
32
+ TRACE = 0
33
+ DEBUG = 1
34
+ INFO = 2
35
+ end
36
+
37
+ # Permanently disable kernel logging output for this process.
38
+ # Once called, logging cannot be re-enabled.
39
+ # This function should only be called once and is not thread-safe.
40
+ def self.disable
41
+ BitcoinKernel.btck_logging_disable
42
+ end
43
+
44
+ # Set logging format options.
45
+ # @param [Hash] opts Logging options
46
+ # @option opts [Boolean] :timestamps Prepend timestamp to log messages
47
+ # @option opts [Boolean] :time_micros Log timestamps in microsecond precision
48
+ # @option opts [Boolean] :threadnames Prepend thread name to log messages
49
+ # @option opts [Boolean] :sourcelocations Prepend source location to log messages
50
+ # @option opts [Boolean] :category_levels Prepend log category and level to log messages
51
+ def self.set_options(timestamps: false, time_micros: false, threadnames: false,
52
+ sourcelocations: false, category_levels: false)
53
+ opts = Options.new
54
+ opts[:log_timestamps] = timestamps ? 1 : 0
55
+ opts[:log_time_micros] = time_micros ? 1 : 0
56
+ opts[:log_threadnames] = threadnames ? 1 : 0
57
+ opts[:log_sourcelocations] = sourcelocations ? 1 : 0
58
+ opts[:always_print_category_levels] = category_levels ? 1 : 0
59
+ BitcoinKernel.btck_logging_set_options(opts)
60
+ end
61
+
62
+ # Set log level for a specific category.
63
+ # @param [Integer] category One of Category constants
64
+ # @param [Integer] level One of Level constants
65
+ def self.set_level(category, level)
66
+ BitcoinKernel.btck_logging_set_level_category(category, level)
67
+ end
68
+
69
+ # Enable a log category.
70
+ # @param [Integer] category One of Category constants (use Category::ALL to enable all)
71
+ def self.enable_category(category)
72
+ BitcoinKernel.btck_logging_enable_category(category)
73
+ end
74
+
75
+ # Disable a log category.
76
+ # @param [Integer] category One of Category constants (use Category::ALL to disable all)
77
+ def self.disable_category(category)
78
+ BitcoinKernel.btck_logging_disable_category(category)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Represents a script pubkey.
5
+ class ScriptPubkey < FFI::AutoPointer
6
+ include Serializable
7
+ serialize_with :btck_script_pubkey_to_bytes
8
+
9
+ # Create a ScriptPubkey from raw bytes.
10
+ # @param [String] script Raw script bytes (binary string)
11
+ # @return [ScriptPubkey]
12
+ def self.from_raw(script)
13
+ raw_ptr = FFI::MemoryPointer.new(:uint8, script.bytesize)
14
+ raw_ptr.put_bytes(0, script)
15
+ ptr = BitcoinKernel.btck_script_pubkey_create(raw_ptr, script.bytesize)
16
+ raise Error, "Failed to create script pubkey" if ptr.null?
17
+ new(ptr)
18
+ end
19
+
20
+ # @param [FFI::Pointer] ptr Pointer to btck_ScriptPubkey
21
+ # @param [Boolean] owned Whether this object owns the pointer.
22
+ # When obtained via TransactionOutput#script_pubkey, the pointer is NOT owned
23
+ # and depends on the lifetime of the parent TransactionOutput.
24
+ # In that case, owned should be false to prevent double-free.
25
+ def initialize(ptr, owned: true)
26
+ super(ptr)
27
+ self.autorelease = owned
28
+ end
29
+
30
+ def self.release(ptr)
31
+ BitcoinKernel.btck_script_pubkey_destroy(ptr)
32
+ end
33
+
34
+ # Compare two script pubkeys for equality by their serialized bytes.
35
+ # @param [ScriptPubkey] other The other script pubkey to compare
36
+ # @return [Boolean]
37
+ def ==(other)
38
+ return false unless other.is_a?(ScriptPubkey)
39
+ to_bytes == other.to_bytes
40
+ end
41
+
42
+ # Verify that the script is correctly spent by a transaction input.
43
+ # @param [Integer] amount Amount in satoshis
44
+ # @param [Transaction] tx Transaction spending this script
45
+ # @param [Integer] input_index Index of the input spending this script
46
+ # @param [Array<TransactionOutput>] spent_outputs All spent outputs (required for taproot)
47
+ # @param [Integer] flags Script verification flags (default: ScriptFlags::ALL)
48
+ # @return [Boolean] true if verification succeeded
49
+ def verify(amount:, tx:, input_index:, spent_outputs: [], flags: ScriptFlags::ALL)
50
+ status_ptr = FFI::MemoryPointer.new(:uint8)
51
+
52
+ # Create precomputed transaction data
53
+ precomputed_ptr = nil
54
+ unless spent_outputs.empty?
55
+ outputs_ptr = FFI::MemoryPointer.new(:pointer, spent_outputs.size)
56
+ spent_outputs.each_with_index do |out, i|
57
+ outputs_ptr.put_pointer(i * FFI::Pointer.size, out)
58
+ end
59
+ precomputed_ptr = BitcoinKernel.btck_precomputed_transaction_data_create(
60
+ tx, outputs_ptr, spent_outputs.size
61
+ )
62
+ end
63
+
64
+ begin
65
+ result = BitcoinKernel.btck_script_pubkey_verify(
66
+ self, amount, tx, precomputed_ptr, input_index, flags, status_ptr
67
+ )
68
+ result == 1
69
+ ensure
70
+ BitcoinKernel.btck_precomputed_transaction_data_destroy(precomputed_ptr) if precomputed_ptr
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Mixin for classes that can be serialized to bytes.
5
+ # Include this module and call `serialize_with` to define the to_bytes method.
6
+ #
7
+ # @example
8
+ # class Block < FFI::AutoPointer
9
+ # include Serializable
10
+ # serialize_with :btck_block_to_bytes
11
+ # end
12
+ module Serializable
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+ # Define a to_bytes method using the specified FFI serialization function.
19
+ # @param ffi_method [Symbol] The FFI function name for serialization
20
+ def serialize_with(ffi_method)
21
+ define_method(:to_bytes) do
22
+ buffer = []
23
+ callback = proc do |bytes_ptr, size, _userdata|
24
+ buffer << bytes_ptr.read_bytes(size)
25
+ 0
26
+ end
27
+ BitcoinKernel.send(ffi_method, self, callback, nil)
28
+ buffer.join.b
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Represents a Bitcoin transaction.
5
+ class Transaction < FFI::AutoPointer
6
+ include Serializable
7
+ serialize_with :btck_transaction_to_bytes
8
+
9
+ # Create a Transaction from raw serialized data.
10
+ # @param [String] raw_tx Serialized transaction data (binary string)
11
+ # @return [Transaction]
12
+ def self.from_raw(raw_tx)
13
+ raw_ptr = FFI::MemoryPointer.new(:uint8, raw_tx.bytesize)
14
+ raw_ptr.put_bytes(0, raw_tx)
15
+ ptr = BitcoinKernel.btck_transaction_create(raw_ptr, raw_tx.bytesize)
16
+ raise Error, "Failed to create transaction from raw data" if ptr.null?
17
+ new(ptr)
18
+ end
19
+
20
+ def self.release(ptr)
21
+ BitcoinKernel.btck_transaction_destroy(ptr)
22
+ end
23
+
24
+ # Create a copy of this transaction.
25
+ # @return [Transaction] A new Transaction instance with copied data
26
+ def copy
27
+ copied_ptr = BitcoinKernel.btck_transaction_copy(self)
28
+ raise Error, "Failed to copy transaction" if copied_ptr.null?
29
+ Transaction.new(copied_ptr)
30
+ end
31
+
32
+ # Compare two transactions for equality by their txid.
33
+ # @param [Transaction] other The other transaction to compare
34
+ # @return [Boolean]
35
+ def ==(other)
36
+ return false unless other.is_a?(Transaction)
37
+ txid == other.txid
38
+ end
39
+
40
+ # Number of inputs in the transaction.
41
+ # @return [Integer]
42
+ def input_count
43
+ BitcoinKernel.btck_transaction_count_inputs(self)
44
+ end
45
+
46
+ # Number of outputs in the transaction.
47
+ # @return [Integer]
48
+ def output_count
49
+ BitcoinKernel.btck_transaction_count_outputs(self)
50
+ end
51
+
52
+ # Get output at specified index.
53
+ # @param [Integer] index Output index
54
+ # @return [TransactionOutput]
55
+ def output_at(index)
56
+ out_ptr = BitcoinKernel.btck_transaction_get_output_at(self, index)
57
+ raise Error, "Output not found at index #{index}" if out_ptr.null?
58
+ TransactionOutput.new(out_ptr, owned: false)
59
+ end
60
+
61
+ # Get all outputs.
62
+ # @return [Array<TransactionOutput>]
63
+ def outputs
64
+ output_count.times.map { |i| output_at(i) }
65
+ end
66
+
67
+ # Get input at specified index.
68
+ # @param [Integer] index Input index
69
+ # @return [TransactionInput]
70
+ def input_at(index)
71
+ in_ptr = BitcoinKernel.btck_transaction_get_input_at(self, index)
72
+ raise Error, "Input not found at index #{index}" if in_ptr.null?
73
+ TransactionInput.new(in_ptr, owned: false)
74
+ end
75
+
76
+ # Get all inputs.
77
+ # @return [Array<TransactionInput>]
78
+ def inputs
79
+ input_count.times.map { |i| input_at(i) }
80
+ end
81
+
82
+ # Get the txid.
83
+ # @return [Txid]
84
+ def txid
85
+ ptr = BitcoinKernel.btck_transaction_get_txid(self)
86
+ raise Error, "Failed to get txid" if ptr.null?
87
+ Txid.new(ptr, owned: false)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Represents a transaction input.
5
+ class TransactionInput < FFI::AutoPointer
6
+ # @param [FFI::Pointer] ptr Pointer to btck_TransactionInput
7
+ # @param [Boolean] owned Whether this object owns the pointer.
8
+ # When obtained via Transaction#input_at, the pointer is NOT owned
9
+ # and depends on the lifetime of the parent Transaction.
10
+ # In that case, owned should be false to prevent double-free.
11
+ def initialize(ptr, owned: true)
12
+ super(ptr)
13
+ self.autorelease = owned
14
+ end
15
+
16
+ def self.release(ptr)
17
+ BitcoinKernel.btck_transaction_input_destroy(ptr)
18
+ end
19
+
20
+ # Get the outpoint (previous transaction output being spent).
21
+ # @return [TransactionOutPoint]
22
+ def out_point
23
+ out_point_ptr = BitcoinKernel.btck_transaction_input_get_out_point(self)
24
+ raise Error, "Failed to get out point" if out_point_ptr.null?
25
+ TransactionOutPoint.new(out_point_ptr, owned: false)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Represents a transaction outpoint (reference to a previous output).
5
+ class TransactionOutPoint < FFI::AutoPointer
6
+ # @param [FFI::Pointer] ptr Pointer to btck_TransactionOutPoint
7
+ # @param [Boolean] owned Whether this object owns the pointer.
8
+ # When obtained via TransactionInput#out_point, the pointer is NOT owned
9
+ # and depends on the lifetime of the parent TransactionInput.
10
+ # In that case, owned should be false to prevent double-free.
11
+ def initialize(ptr, owned: true)
12
+ super(ptr)
13
+ self.autorelease = owned
14
+ end
15
+
16
+ def self.release(ptr)
17
+ BitcoinKernel.btck_transaction_out_point_destroy(ptr)
18
+ end
19
+
20
+ # Get the index of the output in the previous transaction.
21
+ # @return [Integer]
22
+ def index
23
+ BitcoinKernel.btck_transaction_out_point_get_index(self)
24
+ end
25
+
26
+ # Get the txid of the previous transaction.
27
+ # @return [Txid]
28
+ def txid
29
+ ptr = BitcoinKernel.btck_transaction_out_point_get_txid(self)
30
+ raise Error, "Failed to get txid" if ptr.null?
31
+ Txid.new(ptr, owned: false)
32
+ end
33
+
34
+ # Compare two transaction outpoints for equality by txid and index.
35
+ # @param [TransactionOutPoint] other The other outpoint to compare
36
+ # @return [Boolean]
37
+ def ==(other)
38
+ return false unless other.is_a?(TransactionOutPoint)
39
+ txid == other.txid && index == other.index
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BitcoinKernel
4
+ # Represents a transaction output.
5
+ class TransactionOutput < FFI::AutoPointer
6
+ # @param [FFI::Pointer] ptr Pointer to btck_TransactionOutput
7
+ # @param [Boolean] owned Whether this object owns the pointer.
8
+ # When obtained via Transaction#output_at, the pointer is NOT owned
9
+ # and depends on the lifetime of the parent Transaction.
10
+ # In that case, owned should be false to prevent double-free.
11
+ # If you need to keep the TransactionOutput longer than the Transaction,
12
+ # use btck_transaction_output_copy to create an owned copy.
13
+ def initialize(ptr, owned: true)
14
+ super(ptr)
15
+ self.autorelease = owned
16
+ end
17
+
18
+ def self.release(ptr)
19
+ BitcoinKernel.btck_transaction_output_destroy(ptr)
20
+ end
21
+
22
+ # Get the output amount in satoshis.
23
+ # @return [Integer]
24
+ def amount
25
+ BitcoinKernel.btck_transaction_output_get_amount(self)
26
+ end
27
+
28
+ # Get the script pubkey.
29
+ # @return [ScriptPubkey]
30
+ def script_pubkey
31
+ spk_ptr = BitcoinKernel.btck_transaction_output_get_script_pubkey(self)
32
+ raise Error, "Failed to get script pubkey" if spk_ptr.null?
33
+ ScriptPubkey.new(spk_ptr, owned: false)
34
+ end
35
+
36
+ # Compare two transaction outputs for equality by amount and script pubkey.
37
+ # @param [TransactionOutput] other The other output to compare
38
+ # @return [Boolean]
39
+ def ==(other)
40
+ return false unless other.is_a?(TransactionOutput)
41
+ amount == other.amount && script_pubkey == other.script_pubkey
42
+ end
43
+ end
44
+ end