class Pochette::BaseTransactionBuilder

Builds transactions from a list of source addresses and a list of recipients. Uses Pochette.backend to fetch unspent outputs and related transaction data. Instantiating will perform all the given queries, you'll be left with a TransactionBuilder object that is either valid? or not, and if valid you can query the results via to_hash.

Attributes

addresses[RW]
errors[RW]
inputs[RW]
options[RW]
outputs[RW]

Public Class Methods

backend() click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 11
def self.backend
  raise NotImplementedError
end
new(options) click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 29
def initialize(options)
  self.backend = options[:backend] if options[:backend]
  initialize_options(options)
  initialize_fee
  initialize_outputs
  return unless valid?
  select_utxos
  add_change_output
  validate_final_amounts
end

Public Instance Methods

as_hash() click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 48
def as_hash
  return nil unless valid?
  { input_total: inputs_amount.to_i,
    output_total: outputs_amount.to_i,
    fee: (inputs_amount - outputs_amount).to_i,
    inputs: inputs,
    outputs: outputs,
    utxos_to_blacklist: inputs.collect{|i| [i[1], i[2]] },
  }
end
valid?() click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 59
def valid?
  errors.size == 0
end

Protected Instance Methods

add_change_output() click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 135
def add_change_output
  change = inputs_amount - outputs_amount -
    minimum_fee(fee_for_bytes(output_size))
  change_address = options[:change_address] || addresses.first
  if change > dust_size
    outputs << [change_address, change.to_i]
    add_output_fee
  end
end
add_input_fee(count = 1) click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 83
def add_input_fee(count = 1)
  @minimum_fee += fee_for_bytes(input_size) * count
end
add_output_fee(count = 1) click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 87
def add_output_fee(count = 1)
  @minimum_fee += fee_for_bytes(output_size) * count
end
fee_for_bytes(bytes) click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 95
def fee_for_bytes(bytes)
  bytes.to_d / 1000.to_d * (options[:fee_per_kb] || default_fee_per_kb)
end
initialize_fee() click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 79
def initialize_fee
  @minimum_fee = fee_for_bytes(10)
end
initialize_options(options) click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 73
def initialize_options(options)
  self.options = options
  self.errors ||= []
  self.addresses = options[:addresses]
end
initialize_outputs() click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 99
def initialize_outputs
  self.outputs = options[:outputs]
  if (outputs.nil? || outputs.empty?) && !options[:spend_all]
    errors << :try_with_spend_all
    return
  end

  add_output_fee(outputs.size)
  if outputs.any?{|o| o[1] < dust_size }
    errors << :dust_in_outputs
  end
end
inputs_amount() click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 145
def inputs_amount
  inputs.collect{|x| x[3] }.sum
end
minimum_fee(stage=0) click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 91
def minimum_fee(stage=0)
  [@minimum_fee + stage, network_minimum_fee].max
end
outputs_amount() click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 149
def outputs_amount
  outputs.collect(&:last).sum
end
select_utxos() click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 112
def select_utxos
  utxo_blacklist = options[:utxo_blacklist] || []
  all_utxos = backend.list_unspent(addresses)
  available_utxos = all_utxos.reject do |utxo|
    utxo_blacklist.include?([utxo[1], utxo[2]])
  end
  
  self.inputs = []
  if options[:spend_all]
    self.inputs = available_utxos
    add_input_fee(inputs.size)
  else
    needed = outputs.collect{|o| o.last }.sum
    collected = 0.to_d
    available_utxos.each do |utxo|
      break if collected >= needed + minimum_fee
      collected += utxo[3]
      self.inputs << utxo
      add_input_fee
    end
  end
end
validate_final_amounts() click to toggle source
# File lib/pochette/base_transaction_builder.rb, line 153
def validate_final_amounts
  if inputs_amount < (outputs_amount + minimum_fee)
    errors << :insufficient_funds
  end
end