# Class BSV::Transaction::MerklePath <a id="class-BSV-Transaction-MerklePath"></a>

**Inherits:** `Object`

A BRC-74 merkle path (BUMP — Bitcoin Unified Merkle Path).

Encodes the proof that a transaction is included in a block by storing the
minimum set of intermediate hashes needed to recompute the block's merkle root
from a given transaction ID.

**@example Parse a BUMP from hex and compute the merkle root**
```ruby
mp = BSV::Transaction::MerklePath.from_hex(bump_hex)
root_hex = mp.compute_root_hex(txid_hex)
```

## Attributes
### `block_height` [R] <a id="attribute-i-block_height"></a> <a id="block_height-instance_method"></a>
- **@return** [Integer] the block height this merkle path belongs to

### `path` [R] <a id="attribute-i-path"></a> <a id="path-instance_method"></a>
- **@return** [Array<Array<PathElement>>] tree levels, each an array of leaves

## Public Class Methods
### `from_binary(data, offset = 0)` <a id="method-c-from_binary"></a> <a id="from_binary-class_method"></a>
Deserialise a merkle path from BRC-74 binary format.
- **@param** `data` [String] binary data
- **@param** `offset` [Integer] byte offset to start reading from
- **@return** [Array(MerklePath, Integer)] the merkle path and bytes consumed

### `from_hex(hex)` <a id="method-c-from_hex"></a> <a id="from_hex-class_method"></a>
Deserialise a merkle path from a BRC-74 hex string.
- **@param** `hex` [String] hex-encoded BUMP data
- **@return** [MerklePath] the parsed merkle path

### `from_tsc(dtxid_hex:, index:, nodes:, block_height:)` <a id="method-c-from_tsc"></a> <a id="from_tsc-class_method"></a>
Construct a MerklePath from a TSC (Bitcoin SV "Transaction Status Check")
merkle proof, as returned by the WhatsOnChain +/tx/{txid}/proof/tsc+ endpoint.

The TSC format gives a flat list of sibling hashes (leaf-to-root order), one
per tree level. BRC-74 (BUMP) requires a multi-level structure with explicit
tree positions. This method does the conversion. Getting it wrong (e.g. flat
array in a single level) produces a BUMP that ARC rejects.
- **@param** `dtxid_hex` [String] hex-encoded transaction ID in display byte order
- **@param** `index` [Integer] the transaction's position in the block
- **@param** `nodes` [Array<String>] sibling hashes leaf-to-root, each a 32-byte
hex string in display byte order, or +"*"+ for a duplicate node
- **@param** `block_height` [Integer] the block's height (TSC carries the block
hash; the caller must look up the height separately)
- **@return** [MerklePath] a BRC-74 merkle path equivalent to the TSC proof

**@example Convert a WoC TSC proof**
```ruby
tsc = JSON.parse(woc_response).first
mp = BSV::Transaction::MerklePath.from_tsc(
  dtxid_hex:    tsc['txOrId'],
  index:        tsc['index'],
  nodes:        tsc['nodes'],
  block_height: 612_251
)
mp.compute_root_hex(tsc['txOrId']) #=> the block's merkle root
```

### `merkle_tree_parent(left, right)` <a id="method-c-merkle_tree_parent"></a> <a id="merkle_tree_parent-class_method"></a>
Compute the parent hash of two sibling nodes.
- **@param** `left` [String] 32-byte left child hash
- **@param** `right` [String] 32-byte right child hash
- **@return** [String] 32-byte parent hash (double-SHA-256 of concatenation)

## Public Instance Methods
### `combine(other)` <a id="method-i-combine"></a> <a id="combine-instance_method"></a>
Merge another merkle path into this one.

Both paths must share the same block height and merkle root. After combining,
this path contains the union of all leaves, trimmed to the minimum set
required to prove every txid-flagged leaf. The trim matches the TS SDK's
`combine` behaviour and prevents accumulation of unnecessary sibling hashes
across repeated merges.
- **@param** `other` [MerklePath] the path to merge in
- **@raise** [ArgumentError] if block heights or merkle roots differ
- **@return** [self] for chaining

### `compute_root(wtxid = nil)` <a id="method-i-compute_root"></a> <a id="compute_root-instance_method"></a>
Recompute the merkle root from this path and a transaction ID.
- **@param** `wtxid` [String, nil] 32-byte txid in wire byte order (auto-detected if nil)
- **@raise** [ArgumentError] if the wtxid is not found in the path
- **@return** [String] 32-byte merkle root in wire byte order

### `compute_root_hex(dtxid_hex = nil)` <a id="method-i-compute_root_hex"></a> <a id="compute_root_hex-instance_method"></a>
Recompute the merkle root and return it as a hex string.
- **@param** `dtxid_hex` [String, nil] hex-encoded txid (display order)
- **@return** [String] hex-encoded merkle root (display order)

### `extract(wtxid_hashes)` <a id="method-i-extract"></a> <a id="extract-instance_method"></a>
Extract a minimal compound MerklePath covering only the specified transaction
IDs.

Given a compound path (e.g. one merged from multiple single-leaf proofs in the
same block), this method reconstructs the minimum set of sibling hashes at
each tree level for every requested txid, assembles them into a new trimmed
compound path, and verifies that the extracted path computes the same merkle
root as the source.

The primary use case is +Tx#to_beef+: when a BUMP loaded from a proof store
carries +txid: true+ flags for transactions that are not part of the current
BEEF bundle, extracting only the bundled txids strips the phantom flags (and
the now-unneeded sibling nodes) from the serialised output. See issue #302 for
background.

Matches the TS SDK's <code>MerklePath.extract</code> behaviour.
- **@param** `wtxid_hashes` [Array<String>] 32-byte txids in wire byte
order (reverse of display order). To pass hex strings, use
+dtxid_hexes.map { |h| [h].pack('H*').reverse }+.
- **@raise** [ArgumentError] if +wtxid_hashes+ is empty, any requested
txid is not present in the source path's level 0, or the
extracted path's root does not match the source root
- **@return** [MerklePath] a new trimmed compound path proving only the
requested txids

### `initialize(block_height:, path:)` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
- **@param** `block_height` [Integer] the block height
- **@param** `path` [Array<Array<PathElement>>] tree levels
- **@raise** [ArgumentError] if block_height is negative, path is empty,
any level is not an Array, or level 0 has no txid-flagged leaf
- **@return** [MerklePath] a new instance of MerklePath

### `initialize_copy(source)` <a id="method-i-initialize_copy"></a> <a id="initialize_copy-instance_method"></a>
Produce an independent copy: a new MerklePath whose outer `path` array and
each inner level array can be mutated (via {#combine}, {#trim}, {#extract})
without affecting the original. PathElements themselves are immutable and are
shared between the original and the copy.
- **@param** `source` [MerklePath] the MerklePath being copied from
- **@return** [void]

### `to_binary()` <a id="method-i-to_binary"></a> <a id="to_binary-instance_method"></a>
Serialise the merkle path to BRC-74 binary format.
- **@return** [String] binary BUMP data

### `to_hex()` <a id="method-i-to_hex"></a> <a id="to_hex-instance_method"></a>
Serialise the merkle path to a BRC-74 hex string.
- **@return** [String] hex-encoded BUMP data

### `trim()` <a id="method-i-trim"></a> <a id="trim-instance_method"></a>
Remove all internal nodes that are not required by level zero txid-flagged
leaves. Assumes the path has at least the minimum set of sibling hashes needed
to prove every txid leaf. Leaves each level sorted by increasing offset.

This is the Ruby port of the TypeScript SDK's <code>MerklePath.trim</code>. It
is called implicitly by {#combine} and {#extract} and rarely needs to be
invoked directly.
- **@return** [self] for chaining

### `verify(dtxid_hex, chain_tracker)` <a id="method-i-verify"></a> <a id="verify-instance_method"></a>
Verify that this merkle path is valid for a given transaction.

Computes the merkle root from the path and txid, then checks it against the
blockchain via the provided chain tracker.

For coinbase transactions (offset 0 in the merkle tree), an additional
maturity check is performed: the coinbase must have at least 100 confirmations
before it is considered spendable/valid.

NOTE: The TS SDK has an inverted coinbase maturity check at MerklePath.ts:378
(`this.blockHeight + 100 < height`), which rejects mature coinbase
transactions and accepts immature ones — the opposite of the intended
behaviour. The correct logic is: reject when `current_height - block_height <
100` (immature).
- **@param** `dtxid_hex` [String] hex-encoded transaction ID (display order)
- **@param** `chain_tracker` [Transaction::ChainTracker] chain tracker to verify the root against
- **@return** [Boolean] true if the computed root matches the block at this height
