bsv-sdk 0.24.0 → 0.25.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.
@@ -72,10 +72,10 @@ module BSV
72
72
 
73
73
  # A BEEF entry containing a raw transaction without a merkle proof.
74
74
  class RawTxEntry < BeefTx
75
- # @return [Tx] the transaction
75
+ # @return [Transaction::Tx] the transaction
76
76
  attr_reader :transaction
77
77
 
78
- # @param transaction [Tx] the transaction
78
+ # @param transaction [Transaction::Tx] the transaction
79
79
  # @raise [ArgumentError] if transaction is nil
80
80
  def initialize(transaction:)
81
81
  raise ArgumentError, 'RawTxEntry requires a transaction' if transaction.nil?
@@ -97,13 +97,13 @@ module BSV
97
97
 
98
98
  # A BEEF entry containing a raw transaction with an associated BUMP index.
99
99
  class ProvenTxEntry < BeefTx
100
- # @return [Tx] the transaction
100
+ # @return [Transaction::Tx] the transaction
101
101
  attr_reader :transaction
102
102
 
103
103
  # @return [Integer] index into the BEEF bumps array
104
104
  attr_reader :bump_index
105
105
 
106
- # @param transaction [Tx] the transaction
106
+ # @param transaction [Transaction::Tx] the transaction
107
107
  # @param bump_index [Integer] index into the bumps array
108
108
  # @raise [ArgumentError] if transaction or bump_index is nil
109
109
  def initialize(transaction:, bump_index:)
@@ -188,7 +188,7 @@ module BSV
188
188
  # After parsing, input source transactions are wired automatically.
189
189
  #
190
190
  # @param data [String] raw BEEF binary
191
- # @return [Beef] the parsed BEEF bundle
191
+ # @return [Transaction::Beef] the parsed BEEF bundle
192
192
  def self.from_binary(data)
193
193
  raise ArgumentError, "truncated BEEF: need at least 4 bytes for version, got #{data.bytesize}" if data.bytesize < 4
194
194
 
@@ -247,7 +247,7 @@ module BSV
247
247
  # Deserialise a BEEF bundle from a hex string.
248
248
  #
249
249
  # @param hex [String] hex-encoded BEEF data
250
- # @return [Beef] the parsed BEEF bundle
250
+ # @return [Transaction::Beef] the parsed BEEF bundle
251
251
  def self.from_hex(hex)
252
252
  from_binary(BSV::Primitives::Hex.decode(hex, name: 'BEEF hex'))
253
253
  end
@@ -320,7 +320,7 @@ module BSV
320
320
  # Find a transaction in the bundle by its wire-order transaction ID.
321
321
  #
322
322
  # @param wtxid [String] 32-byte wire-order wtxid
323
- # @return [Tx, nil] the matching transaction, or nil
323
+ # @return [Transaction::Tx, nil] the matching transaction, or nil
324
324
  def find_transaction(wtxid)
325
325
  BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
326
326
  BSV.logger&.debug { "[Beef] find_transaction: #{wtxid.reverse.unpack1('H*')} in #{@transactions.length} entries" }
@@ -353,7 +353,7 @@ module BSV
353
353
  # Find a transaction with all source_transactions wired for signing.
354
354
  #
355
355
  # @param wtxid [String] 32-byte wire-order wtxid
356
- # @return [Tx, nil] the transaction with wired inputs, or nil
356
+ # @return [Transaction::Tx, nil] the transaction with wired inputs, or nil
357
357
  def find_transaction_for_signing(wtxid)
358
358
  BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
359
359
  tx = find_transaction(wtxid)
@@ -367,7 +367,7 @@ module BSV
367
367
  # and merkle paths) for atomic proof validation.
368
368
  #
369
369
  # @param wtxid [String] 32-byte wire-order wtxid
370
- # @return [Tx, nil] the transaction with full proof tree, or nil
370
+ # @return [Transaction::Tx, nil] the transaction with full proof tree, or nil
371
371
  def find_atomic_transaction(wtxid)
372
372
  BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
373
373
  tx = find_transaction(wtxid)
@@ -385,6 +385,161 @@ module BSV
385
385
  to_atomic_binary(subject_wtxid).unpack1('H*')
386
386
  end
387
387
 
388
+ # --- Supporting methods for multi-party BEEF exchange ---
389
+
390
+ # Add a TXID-only entry for +wtxid+ if no entry exists yet.
391
+ #
392
+ # If an entry already exists (in any format), the call is a no-op —
393
+ # TXID-only is the weakest format, so an existing stronger entry is kept.
394
+ #
395
+ # @param wtxid [String] 32-byte wire-order binary txid
396
+ # @return [BeefTx] the existing or newly added entry
397
+ def merge_txid_only(wtxid)
398
+ BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
399
+ existing = @transactions.find { |bt| bt.wtxid == wtxid }
400
+ return existing if existing
401
+
402
+ entry = TxidOnlyEntry.new(known_wtxid: wtxid)
403
+ @transactions << entry
404
+ entry
405
+ end
406
+
407
+ # Return a shallow copy of this Transaction::Beef.
408
+ #
409
+ # Both +@bumps+ and +@transactions+ arrays are duplicated (new arrays),
410
+ # but the +BeefTx+ and +MerklePath+ objects they contain are shared
411
+ # references. This mirrors the TS SDK's +clone+ contract: entries are
412
+ # effectively immutable once added to a bundle, so shallow semantics are
413
+ # correct. If a deeper copy is ever required, add a separate +deep_dup+
414
+ # rather than changing this method's contract.
415
+ #
416
+ # +@subject_wtxid+ (Atomic BEEF) and +@txs_not_valid+ (cyclic-graph
417
+ # metadata) are preserved on the copy.
418
+ #
419
+ # @return [Transaction::Beef] a new shallow copy
420
+ def clone
421
+ # Use super (Object#clone) rather than self.class.new so subclasses
422
+ # (e.g. Transaction::BeefParty) with different initialize signatures
423
+ # work correctly. Object#clone does a shallow ivar copy without
424
+ # invoking initialize; we then dup the two arrays so the copy's
425
+ # contents can mutate independently.
426
+ c = super
427
+ c.instance_variable_set(:@bumps, @bumps.dup)
428
+ c.instance_variable_set(:@transactions, @transactions.dup)
429
+ c.instance_variable_set(:@txs_not_valid, @txs_not_valid&.dup)
430
+ c
431
+ end
432
+
433
+ # Return a new Transaction::Beef with TXID-only entries removed for any
434
+ # wtxid in +known_wtxids+.
435
+ #
436
+ # RAW_TX and RAW_TX_AND_BUMP entries are always retained, even when their
437
+ # wtxid appears in +known_wtxids+. Only +TxidOnlyEntry+ records are
438
+ # candidates for removal (they carry no proof data the recipient needs).
439
+ #
440
+ # After dropping TXID-only entries, any BUMP that is no longer referenced
441
+ # by any remaining +ProvenTxEntry+ is removed, and all +bump_index+
442
+ # fields are renumbered to match the new bumps array (mirrors TS
443
+ # +trimKnownTxids+ at +Beef.ts:861-914+).
444
+ #
445
+ # Does not mutate +self+. Starts from a {#clone} so the caller's state
446
+ # is preserved.
447
+ #
448
+ # @param known_wtxids [Array<String>] binary wtxids the recipient already has
449
+ # @return [Transaction::Beef] a new bundle with the specified entries trimmed
450
+ def trim_known_wtxids(known_wtxids)
451
+ known_set = known_wtxids.to_set
452
+
453
+ trimmed = clone
454
+ trimmed.instance_variable_set(
455
+ :@transactions,
456
+ trimmed.transactions.reject { |bt| bt.is_a?(TxidOnlyEntry) && known_set.include?(bt.wtxid) }
457
+ )
458
+
459
+ # Find which bump indices are still referenced
460
+ referenced = Set.new
461
+ trimmed.transactions.each do |bt|
462
+ referenced.add(bt.bump_index) if bt.is_a?(ProvenTxEntry)
463
+ end
464
+
465
+ # If all bumps are still referenced, nothing more to do
466
+ return trimmed if referenced.size == trimmed.bumps.length
467
+
468
+ # Build old → new index map for surviving bumps
469
+ index_map = {}
470
+ new_idx = 0
471
+ trimmed.bumps.each_with_index do |_, old_idx|
472
+ if referenced.include?(old_idx)
473
+ index_map[old_idx] = new_idx
474
+ new_idx += 1
475
+ end
476
+ end
477
+
478
+ # Drop unreferenced bumps
479
+ trimmed.instance_variable_set(
480
+ :@bumps,
481
+ trimmed.bumps.each_with_index.filter_map { |bump, i| bump if referenced.include?(i) }
482
+ )
483
+
484
+ # Renumber bump_index on all ProvenTxEntry records
485
+ trimmed.instance_variable_set(
486
+ :@transactions,
487
+ trimmed.transactions.map do |bt|
488
+ next bt unless bt.is_a?(ProvenTxEntry)
489
+
490
+ new_bump_idx = index_map.fetch(bt.bump_index)
491
+ ProvenTxEntry.new(transaction: bt.transaction, bump_index: new_bump_idx).tap do |e|
492
+ e.transaction.merkle_path = trimmed.bumps[new_bump_idx]
493
+ end
494
+ end
495
+ )
496
+
497
+ trimmed
498
+ end
499
+
500
+ # Return the wtxids of transactions that are "valid" in this bundle.
501
+ #
502
+ # A transaction is valid when it either:
503
+ # - has a merkle proof (is a +ProvenTxEntry+), or
504
+ # - all of its inputs chain back to proven transactions within the bundle.
505
+ #
506
+ # TXID-only entries are excluded. Cyclic transactions (from
507
+ # +@txs_not_valid+) are also excluded.
508
+ #
509
+ # Used by {Transaction::BeefParty} to record which wtxids a party gains
510
+ # knowledge of after a {#merge_beef_from_party} call.
511
+ #
512
+ # @return [Array<String>] binary wire-order wtxids
513
+ def valid_wtxids
514
+ invalid = @txs_not_valid || Set.new
515
+ known = Set.new
516
+
517
+ # Seed with proven entries (excluding any marked cyclic/unsortable)
518
+ @transactions.each do |bt|
519
+ known.add(bt.wtxid) if bt.is_a?(ProvenTxEntry) && !invalid.include?(bt.wtxid)
520
+ end
521
+
522
+ # Iteratively resolve unproven entries whose inputs are all known.
523
+ # Skip @txs_not_valid entries — by definition they can't be ordered
524
+ # for validation, so a counterparty receiving them can't validate either.
525
+ changed = true
526
+ while changed
527
+ changed = false
528
+ @transactions.each do |bt|
529
+ next if bt.is_a?(TxidOnlyEntry) || known.include?(bt.wtxid)
530
+ next if invalid.include?(bt.wtxid)
531
+ next unless bt.respond_to?(:transaction)
532
+
533
+ if bt.transaction.inputs.all? { |inp| known.include?(inp.prev_wtxid) }
534
+ known.add(bt.wtxid)
535
+ changed = true
536
+ end
537
+ end
538
+ end
539
+
540
+ known.to_a
541
+ end
542
+
388
543
  # --- Merge operations ---
389
544
 
390
545
  # Add or deduplicate a merkle path (BUMP) in this BEEF bundle.
@@ -440,7 +595,7 @@ module BSV
440
595
  # (same txid) are upgraded if a stronger format is now available (F5.7):
441
596
  # TXID_ONLY → RAW_TX or RAW_TX_AND_BUMP; RAW_TX → RAW_TX_AND_BUMP.
442
597
  #
443
- # @param tx [Tx] the transaction to merge
598
+ # @param tx [Transaction::Tx] the transaction to merge
444
599
  # @return [BeefTx] the (possibly existing or upgraded) BeefTx entry
445
600
  def merge_transaction(tx)
446
601
  wtxid = tx.wtxid
@@ -512,7 +667,7 @@ module BSV
512
667
  # BUMP indices are remapped during merge. New BeefTx instances are
513
668
  # constructed rather than sharing references with the source bundle (F5.9).
514
669
  #
515
- # @param other [Beef] the BEEF bundle to merge from
670
+ # @param other [Transaction::Beef] the BEEF bundle to merge from
516
671
  # @return [self]
517
672
  # @raise [ArgumentError] if a transaction in +other+ has a +bump_index+
518
673
  # that does not point to any BUMP in +other.bumps+ (i.e. the source
@@ -800,8 +955,7 @@ module BSV
800
955
 
801
956
  input.source_transaction = source
802
957
  BSV.logger&.debug do
803
- "[Beef] wired input #{input.prev_wtxid.reverse.unpack1('H*')}:#{input.prev_tx_out_index} " \
804
- "-> source #{source.wtxid.reverse.unpack1('H*')}"
958
+ "[Beef] wired input #{input.dtxid_hex}:#{input.prev_tx_out_index} -> source #{source.dtxid}"
805
959
  end
806
960
  end
807
961
 
@@ -821,7 +975,7 @@ module BSV
821
975
  # RAW_TX → RAW_TX_AND_BUMP (if bump now available)
822
976
  #
823
977
  # @param existing [BeefTx] the current entry in @transactions
824
- # @param tx [Tx, nil] the raw transaction (may be nil for TXID_ONLY → TXID_ONLY)
978
+ # @param tx [Transaction::Tx, nil] the raw transaction (may be nil for TXID_ONLY → TXID_ONLY)
825
979
  # @param bump_index [Integer, nil] a BUMP index already validated against @bumps
826
980
  # @return [BeefTx, nil] the upgraded entry, or nil when no upgrade is needed
827
981
  def upgrade_beef_tx(existing, tx, bump_index: nil)
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Transaction
5
+ # Transaction::Beef subclass that tracks per-party knowledge of wtxids.
6
+ #
7
+ # Used in multi-party BEEF exchange to avoid re-transmitting transaction
8
+ # data and proofs that a counterparty already possesses. Each party is
9
+ # identified by a caller-supplied string and associated with the set of
10
+ # wtxids it is known to hold.
11
+ #
12
+ # @example Two-party exchange
13
+ # party = BSV::Transaction::BeefParty.new(['alice', 'bob'])
14
+ # party.merge_beef_from_party('alice', alice_beef)
15
+ # trimmed = party.trimmed_beef_for_party('bob') # plain Transaction::Beef
16
+ class BeefParty < Beef
17
+ # @param initial_parties [Array<String>] optional initial party identifiers
18
+ # @raise [ArgumentError] if +initial_parties+ contains duplicates
19
+ #
20
+ # Defaults to BEEF_V2 (BRC-96) because TXID-only entries — which are
21
+ # central to BeefParty's purpose — are only valid in V2. Matches the TS
22
+ # SDK's +new Beef()+ default.
23
+ def initialize(initial_parties = [])
24
+ super(version: BEEF_V2)
25
+ @party_knowledge = {}
26
+ initial_parties.each { |p| add_party(p) }
27
+ end
28
+
29
+ # @param party_id [String]
30
+ # @return [Boolean] true if +party_id+ has been added
31
+ def party?(party_id)
32
+ @party_knowledge.key?(party_id)
33
+ end
34
+
35
+ # Add a new unique party identifier.
36
+ #
37
+ # @param party_id [String]
38
+ # @raise [ArgumentError] if +party_id+ is already known
39
+ def add_party(party_id)
40
+ raise ArgumentError, "duplicate party #{party_id}" if party?(party_id)
41
+
42
+ @party_knowledge[party_id] = Set.new
43
+ end
44
+
45
+ # Record additional wtxids as known to +party_id+.
46
+ #
47
+ # Auto-creates the party if it is not yet known (TS parity). Also
48
+ # merges a TXID-only entry into the underlying bundle for each wtxid.
49
+ #
50
+ # @param party_id [String] party identifier (auto-created if unknown)
51
+ # @param wtxids [Array<String>] 32-byte wire-order binary wtxids
52
+ # @raise [ArgumentError] if any element of +wtxids+ is not a valid wtxid
53
+ def add_known_wtxids_for_party(party_id, wtxids)
54
+ add_party(party_id) unless party?(party_id)
55
+ wtxids.each do |wtxid|
56
+ BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
57
+ @party_knowledge[party_id].add(wtxid)
58
+ merge_txid_only(wtxid)
59
+ end
60
+ end
61
+
62
+ # @param party_id [String]
63
+ # @return [Array<String>] 32-byte wire-order binary wtxids known to +party_id+
64
+ # @raise [ArgumentError] if +party_id+ is unknown
65
+ def known_wtxids_for_party(party_id)
66
+ raise ArgumentError, "unknown party #{party_id}" unless party?(party_id)
67
+
68
+ @party_knowledge[party_id].to_a
69
+ end
70
+
71
+ # Merge a Transaction::Beef received from +party_id+.
72
+ #
73
+ # Merges all transactions and BUMPs from +beef_or_binary+ into +self+,
74
+ # then records the valid wtxids of the merged bundle as known to
75
+ # +party_id+. Auto-creates the party if not yet known.
76
+ #
77
+ # @param party_id [String] party identifier
78
+ # @param beef_or_binary [Transaction::Beef, String] a Transaction::Beef
79
+ # instance or raw binary BEEF bytes
80
+ def merge_beef_from_party(party_id, beef_or_binary)
81
+ beef = beef_or_binary.is_a?(Beef) ? beef_or_binary : Beef.from_binary(beef_or_binary)
82
+ # Capture the set of wtxids the party actually sent before merging.
83
+ beef_wtxids = beef.transactions.map(&:wtxid)
84
+ merge(beef)
85
+ # Record knowledge of any tx the party sent that is now provably valid
86
+ # against the merged state. This captures the cross-bundle case where
87
+ # an unproven tx in +beef+ becomes valid via inputs already proven in
88
+ # +self+ (and conversely doesn't record knowledge the party never sent).
89
+ known = valid_wtxids & beef_wtxids
90
+ add_known_wtxids_for_party(party_id, known)
91
+ end
92
+
93
+ # Return a new Transaction::Beef (not Transaction::BeefParty) with
94
+ # TXID-only entries that are known to +party_id+ removed.
95
+ #
96
+ # RAW_TX and RAW_TX_AND_BUMP entries are always retained even when the
97
+ # party knows the txid — those formats carry proof data. BUMP indices
98
+ # are renumbered if any unreferenced bumps are dropped.
99
+ #
100
+ # Does not mutate +self+.
101
+ #
102
+ # @param party_id [String]
103
+ # @return [Transaction::Beef] a new trimmed bundle (plain Beef, not BeefParty)
104
+ # @raise [ArgumentError] if +party_id+ is unknown
105
+ def trimmed_beef_for_party(party_id)
106
+ raise ArgumentError, "unknown party #{party_id}" unless party?(party_id)
107
+
108
+ known = @party_knowledge[party_id].to_a
109
+
110
+ # Build a plain Beef from our current state so trim_known_wtxids
111
+ # returns a Beef, not a BeefParty.
112
+ plain = Beef.new(version: @version, bumps: @bumps.dup, transactions: @transactions.dup)
113
+ plain.instance_variable_set(:@subject_wtxid, @subject_wtxid)
114
+ plain.instance_variable_set(:@txs_not_valid, @txs_not_valid&.dup)
115
+ plain.trim_known_wtxids(known)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -56,7 +56,7 @@ module BSV
56
56
  # Return a default ChainTracker backed by the GorillaPool provider.
57
57
  #
58
58
  # @param testnet [Boolean] when true, uses the testnet provider
59
- # @return [ChainTracker]
59
+ # @return [Transaction::ChainTracker]
60
60
  def self.default(testnet: false)
61
61
  new(BSV::Network::Providers::GorillaPool.default(testnet: testnet))
62
62
  end
@@ -17,7 +17,7 @@ module BSV
17
17
  class FeeModel
18
18
  # Compute the fee for a transaction.
19
19
  #
20
- # @param transaction [Tx] the transaction to compute the fee for
20
+ # @param transaction [Transaction::Tx] the transaction to compute the fee for
21
21
  # @return [Integer] the fee in satoshis
22
22
  # @raise [NotImplementedError] if not overridden by a subclass
23
23
  def compute_fee(_transaction)
@@ -66,7 +66,7 @@ module BSV
66
66
 
67
67
  # Compute the fee for a transaction using the latest ARC rate.
68
68
  #
69
- # @param transaction [Tx] the transaction to compute the fee for
69
+ # @param transaction [Transaction::Tx] the transaction to compute the fee for
70
70
  # @return [Integer] the fee in satoshis
71
71
  def compute_fee(transaction)
72
72
  rate = current_rate
@@ -23,7 +23,7 @@ module BSV
23
23
 
24
24
  # Compute the fee for a transaction based on its estimated size.
25
25
  #
26
- # @param transaction [Tx] the transaction to compute the fee for
26
+ # @param transaction [Transaction::Tx] the transaction to compute the fee for
27
27
  # @return [Integer] the fee in satoshis
28
28
  def compute_fee(transaction)
29
29
  size = transaction.estimated_size
@@ -322,7 +322,7 @@ module BSV
322
322
  # logic is: reject when `current_height - block_height < 100` (immature).
323
323
  #
324
324
  # @param dtxid_hex [String] hex-encoded transaction ID (display order)
325
- # @param chain_tracker [ChainTracker] chain tracker to verify the root against
325
+ # @param chain_tracker [Transaction::ChainTracker] chain tracker to verify the root against
326
326
  # @return [Boolean] true if the computed root matches the block at this height
327
327
  def verify(dtxid_hex, chain_tracker)
328
328
  BSV::Primitives::Hex.validate_dtxid_hex!(dtxid_hex, name: 'dtxid_hex')
@@ -24,7 +24,7 @@ module BSV
24
24
 
25
25
  # Generate the P2PKH unlocking script for the given input.
26
26
  #
27
- # @param tx [Tx] the transaction being signed
27
+ # @param tx [Transaction::Tx] the transaction being signed
28
28
  # @param input_index [Integer] the input index to sign
29
29
  # @return [Script::Script] the unlocking script (signature + pubkey)
30
30
  def sign(tx, input_index)
@@ -26,7 +26,7 @@ module BSV
26
26
  # @return [Script::Script, nil] locking script of the source output (needed for sighash)
27
27
  attr_accessor :source_locking_script
28
28
 
29
- # @return [Tx, nil] the full source transaction (for BEEF wiring)
29
+ # @return [Transaction::Tx, nil] the full source transaction (for BEEF wiring)
30
30
  attr_accessor :source_transaction
31
31
 
32
32
  # @return [UnlockingScriptTemplate, nil] template for deferred signing
@@ -59,7 +59,7 @@ module BSV
59
59
 
60
60
  # Append a transaction input.
61
61
  #
62
- # @param input [TransactionInput] the input to add
62
+ # @param input [Transaction::TransactionInput] the input to add
63
63
  # @return [self] for chaining
64
64
  def add_input(input)
65
65
  @inputs << input
@@ -68,7 +68,7 @@ module BSV
68
68
 
69
69
  # Append a transaction output.
70
70
  #
71
- # @param output [TransactionOutput] the output to add
71
+ # @param output [Transaction::TransactionOutput] the output to add
72
72
  # @return [self] for chaining
73
73
  def add_output(output)
74
74
  @outputs << output
@@ -144,7 +144,7 @@ module BSV
144
144
  # Deserialise a transaction from binary data.
145
145
  #
146
146
  # @param data [String] raw binary transaction
147
- # @return [Tx] the parsed transaction
147
+ # @return [Transaction::Tx] the parsed transaction
148
148
  def self.from_binary(data)
149
149
  raise ArgumentError, "truncated transaction: need at least 10 bytes, got #{data.bytesize}" if data.bytesize < 10
150
150
 
@@ -182,7 +182,7 @@ module BSV
182
182
  # Deserialise a transaction from a hex string.
183
183
  #
184
184
  # @param hex [String] hex-encoded transaction
185
- # @return [Tx] the parsed transaction
185
+ # @return [Transaction::Tx] the parsed transaction
186
186
  def self.from_hex(hex)
187
187
  from_binary(BSV::Primitives::Hex.decode(hex, name: 'transaction hex'))
188
188
  end
@@ -190,7 +190,7 @@ module BSV
190
190
  # Deserialise a transaction from Extended Format (BRC-30) binary data.
191
191
  #
192
192
  # @param data [String] raw EF binary
193
- # @return [Tx] the parsed transaction with source data on inputs
193
+ # @return [Transaction::Tx] the parsed transaction with source data on inputs
194
194
  # @raise [ArgumentError] if the EF marker is invalid
195
195
  def self.from_ef(data)
196
196
  raise ArgumentError, "truncated EF transaction: need at least 10 bytes, got #{data.bytesize}" if data.bytesize < 10
@@ -250,7 +250,7 @@ module BSV
250
250
  # Deserialise a transaction from an Extended Format hex string.
251
251
  #
252
252
  # @param hex [String] hex-encoded EF transaction
253
- # @return [Tx] the parsed transaction with source data on inputs
253
+ # @return [Transaction::Tx] the parsed transaction with source data on inputs
254
254
  def self.from_ef_hex(hex)
255
255
  from_ef(BSV::Primitives::Hex.decode(hex, name: 'EF transaction hex'))
256
256
  end
@@ -260,7 +260,7 @@ module BSV
260
260
  #
261
261
  # @param data [String] binary data containing the transaction
262
262
  # @param offset [Integer] byte offset to start reading from
263
- # @return [Array(Tx, Integer)] the transaction and bytes consumed
263
+ # @return [Array(Transaction::Tx, Integer)] the transaction and bytes consumed
264
264
  def self.from_binary_with_offset(data, offset = 0)
265
265
  if data.bytesize < offset + 10
266
266
  raise ArgumentError, "truncated transaction: need at least 10 bytes at offset #{offset}, got #{data.bytesize - offset}"
@@ -370,7 +370,7 @@ module BSV
370
370
  # +wire_source_transactions+ pass in +Beef.from_binary+.
371
371
  #
372
372
  # @param data [String] raw BEEF binary
373
- # @return [Tx, nil] the subject transaction with ancestry wired,
373
+ # @return [Transaction::Tx, nil] the subject transaction with ancestry wired,
374
374
  # or nil if the BEEF is empty or contains no raw transaction entries
375
375
  def self.from_beef(data)
376
376
  beef = Beef.from_binary(data)
@@ -384,7 +384,7 @@ module BSV
384
384
  # Parse a BEEF hex string and return the subject transaction.
385
385
  #
386
386
  # @param hex [String] hex-encoded BEEF
387
- # @return [Tx, nil] the subject transaction with ancestry wired,
387
+ # @return [Transaction::Tx, nil] the subject transaction with ancestry wired,
388
388
  # or nil if the BEEF is empty or contains no raw transaction entries
389
389
  def self.from_beef_hex(hex)
390
390
  from_beef(BSV::Primitives::Hex.decode(hex, name: 'BEEF hex'))
@@ -622,7 +622,7 @@ module BSV
622
622
  # - +:script_failure+ — Ruby raises; TS/Python return +false+; Go also propagates errors
623
623
  # - +:missing_source+ — Ruby raises; consistent with TS/Go/Python (all raise/error)
624
624
  #
625
- # @param chain_tracker [ChainTracker] chain tracker for merkle root validation
625
+ # @param chain_tracker [Transaction::ChainTracker] chain tracker for merkle root validation
626
626
  # @param fee_model [FeeModel, nil] optional fee model to validate the root transaction's fee
627
627
  # @return [true] on successful verification
628
628
  # @raise [VerificationError] with code +:invalid_merkle_proof+ if a merkle proof is invalid
@@ -806,7 +806,7 @@ module BSV
806
806
  # is required. Otherwise, requires a wired +source_transaction+ and a
807
807
  # valid output at +prev_tx_out_index+.
808
808
  #
809
- # @param input [TransactionInput]
809
+ # @param input [Transaction::TransactionInput]
810
810
  # @param idx [Integer] input index, used in error messages
811
811
  # @return [TransactionOutput, nil]
812
812
  def ef_source_output(input, idx)
@@ -14,7 +14,7 @@ module BSV
14
14
  class UnlockingScriptTemplate
15
15
  # Generate the unlocking script for a transaction input.
16
16
  #
17
- # @param _tx [Tx] the transaction being signed
17
+ # @param _tx [Transaction::Tx] the transaction being signed
18
18
  # @param _input_index [Integer] the input index to sign
19
19
  # @return [Script::Script] the generated unlocking script
20
20
  # @raise [NotImplementedError] if not overridden by a subclass
@@ -24,7 +24,7 @@ module BSV
24
24
 
25
25
  # Estimate the unlocking script length in bytes (for fee estimation).
26
26
  #
27
- # @param _tx [Tx] the transaction
27
+ # @param _tx [Transaction::Tx] the transaction
28
28
  # @param _input_index [Integer] the input index
29
29
  # @return [Integer] estimated script length in bytes
30
30
  # @raise [NotImplementedError] if not overridden by a subclass
@@ -19,6 +19,7 @@ module BSV
19
19
  autoload :ChainTracker, 'bsv/transaction/chain_tracker'
20
20
  autoload :ChainTrackers, 'bsv/transaction/chain_trackers'
21
21
  autoload :Beef, 'bsv/transaction/beef'
22
+ autoload :BeefParty, 'bsv/transaction/beef_party'
22
23
  autoload :UnlockingScriptTemplate, 'bsv/transaction/unlocking_script_template'
23
24
  autoload :P2PKH, 'bsv/transaction/p2pkh'
24
25
  autoload :Tx, 'bsv/transaction/tx'
data/lib/bsv/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BSV
4
- VERSION = '0.24.0'
4
+ VERSION = '0.25.0'
5
5
  end
data/lib/bsv-sdk.rb CHANGED
@@ -26,6 +26,8 @@ module BSV
26
26
  autoload :Identity, 'bsv/identity'
27
27
  autoload :Registry, 'bsv/registry'
28
28
  autoload :MCP, 'bsv/mcp'
29
+ autoload :Storage, 'bsv/storage'
30
+ autoload :KVStore, 'bsv/kv_store'
29
31
 
30
32
  # Wallet is loaded eagerly to avoid load-path shadowing when bsv-wallet is
31
33
  # also in the bundle (bsv-wallet/lib/bsv/wallet.rb would otherwise win).
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bsv-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.0
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Bettison
@@ -85,6 +85,11 @@ files:
85
85
  - lib/bsv/identity/constants.rb
86
86
  - lib/bsv/identity/identity_parser.rb
87
87
  - lib/bsv/identity/types.rb
88
+ - lib/bsv/kv_store.rb
89
+ - lib/bsv/kv_store/entry.rb
90
+ - lib/bsv/kv_store/global.rb
91
+ - lib/bsv/kv_store/interpreter.rb
92
+ - lib/bsv/kv_store/token.rb
88
93
  - lib/bsv/mcp.rb
89
94
  - lib/bsv/mcp/config.rb
90
95
  - lib/bsv/mcp/server.rb
@@ -118,6 +123,7 @@ files:
118
123
  - lib/bsv/overlay/broadcast_facilitator.rb
119
124
  - lib/bsv/overlay/constants.rb
120
125
  - lib/bsv/overlay/errors.rb
126
+ - lib/bsv/overlay/historian.rb
121
127
  - lib/bsv/overlay/host_reputation_tracker.rb
122
128
  - lib/bsv/overlay/lookup_facilitator.rb
123
129
  - lib/bsv/overlay/lookup_resolver.rb
@@ -152,6 +158,7 @@ files:
152
158
  - lib/bsv/registry/constants.rb
153
159
  - lib/bsv/registry/types.rb
154
160
  - lib/bsv/script.rb
161
+ - lib/bsv/script/bip276.rb
155
162
  - lib/bsv/script/builder.rb
156
163
  - lib/bsv/script/chunk.rb
157
164
  - lib/bsv/script/interpreter/error.rb
@@ -169,8 +176,13 @@ files:
169
176
  - lib/bsv/script/push_drop_template.rb
170
177
  - lib/bsv/script/script.rb
171
178
  - lib/bsv/secp256k1_native.bundle
179
+ - lib/bsv/storage.rb
180
+ - lib/bsv/storage/downloader.rb
181
+ - lib/bsv/storage/errors.rb
182
+ - lib/bsv/storage/utils.rb
172
183
  - lib/bsv/transaction.rb
173
184
  - lib/bsv/transaction/beef.rb
185
+ - lib/bsv/transaction/beef_party.rb
174
186
  - lib/bsv/transaction/chain_tracker.rb
175
187
  - lib/bsv/transaction/chain_trackers.rb
176
188
  - lib/bsv/transaction/chain_trackers/whats_on_chain.rb