Class: BSV::Wallet::ChangeGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/bsv/wallet_interface/change_generator.rb

Overview

Generates BRC-29 change outputs for excess satoshis in a transaction.

Handles the full lifecycle of change output creation:

- Dust-floor enforcement (a change output is only worthwhile if its
  value covers at least 2× the cost to spend it later)
- Optional distribution across multiple outputs with randomised splits
- BRC-29 key derivation metadata attached to each output so the wallet
  can later identify and spend the change

Examples:

Single change output

generator = BSV::Wallet::ChangeGenerator.new(key_deriver: deriver, fee_estimator: estimator)
outputs = generator.generate(excess_satoshis: 5000)
# => [{ satoshis: 5000, locking_script: ..., derivation_prefix: ..., ... }]

Spread across multiple outputs

generator = BSV::Wallet::ChangeGenerator.new(key_deriver: deriver, fee_estimator: estimator, max_outputs: 4)
outputs = generator.generate(excess_satoshis: 10_000)
# => up to 4 outputs summing to 10_000

Constant Summary collapse

BRC29_PROTOCOL_ID =

BRC-29 protocol identifier used for change key derivation.

[2, '3241645161d8'].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key_deriver:, fee_estimator: nil, fee_model: nil, identity_key: nil, max_outputs: 8) ⇒ ChangeGenerator

Returns a new instance of ChangeGenerator.

Parameters:

  • key_deriver (BSV::Wallet::KeyDeriver)

    derives child keys for each change output

  • fee_estimator (BSV::Wallet::FeeEstimator) (defaults to: nil)

    used to compute the dust floor (also accepted as fee_model: for backwards compatibility)

  • identity_key (String, nil) (defaults to: nil)

    override identity key for change derivation; defaults to key_deriver.identity_key when nil

  • max_outputs (Integer) (defaults to: 8)

    upper bound on change outputs (default: 8)

Raises:

  • (ArgumentError)

    if max_outputs is less than 1



39
40
41
42
43
44
45
46
47
# File 'lib/bsv/wallet_interface/change_generator.rb', line 39

def initialize(key_deriver:, fee_estimator: nil, fee_model: nil, identity_key: nil, max_outputs: 8)
  raise ArgumentError, 'max_outputs must be at least 1' unless max_outputs >= 1
  raise ArgumentError, 'provide fee_estimator: or fee_model:, not both' if fee_estimator && fee_model

  @key_deriver = key_deriver
  @fee_model = fee_estimator || fee_model || raise(ArgumentError, 'fee_estimator: (or fee_model:) is required')
  @identity_key_override = identity_key
  @max_outputs = max_outputs
end

Instance Attribute Details

#max_outputsInteger (readonly)

Returns maximum number of change outputs that may be created.

Returns:

  • (Integer)

    maximum number of change outputs that may be created



30
31
32
# File 'lib/bsv/wallet_interface/change_generator.rb', line 30

def max_outputs
  @max_outputs
end

Instance Method Details

#generate(excess_satoshis:, num_existing_outputs: 0, pool_size: nil, change_params: nil) ⇒ Array<Hash>

Generates change outputs for the given excess satoshis.

Returns an empty array when the excess is zero or below the dust floor. Otherwise returns between 1 and #max_outputs output hashes, each with:

- +:satoshis+ — value of the output
- +:locking_script+ — P2PKH locking script for the derived key
- +:derivation_prefix+ — random hex string for BRC-29 key derivation
- +:derivation_suffix+ — random hex string for BRC-29 key derivation
- +:sender_identity_key+ — wallet's own identity key (self-payment)

When pool_size: and change_params: are both provided, the number of change outputs is adjusted based on the pool’s health:

- Pool below target count: produce more outputs (up to +max_outputs+)
  to build up the UTXO pool.
- Pool at or above target count: produce fewer outputs (1-2) to avoid
  unnecessary fragmentation.

Parameters:

  • excess_satoshis (Integer)

    satoshis remaining after inputs minus outputs minus fee

  • num_existing_outputs (Integer) (defaults to: 0)

    number of outputs already in the transaction (unused here but kept for interface symmetry with future callers)

  • pool_size (Integer, nil) (defaults to: nil)

    current number of spendable UTXOs in the pool

  • change_params (Hash, nil) (defaults to: nil)

    pool targets with :count and :satoshis keys

Returns:

  • (Array<Hash>)

    change output descriptors (may be empty)



72
73
74
75
76
77
78
79
# File 'lib/bsv/wallet_interface/change_generator.rb', line 72

def generate(excess_satoshis:, num_existing_outputs: 0, pool_size: nil, change_params: nil) # rubocop:disable Lint/UnusedMethodArgument
  return [] if excess_satoshis <= 0
  return [] if excess_satoshis < dust_floor

  effective_max = pool_aware_max_outputs(pool_size, change_params)
  amounts = split_amounts(excess_satoshis, effective_max)
  amounts.map { |amount| build_output(amount) }
end