straight 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +5 -1
- data/README.md +20 -10
- data/VERSION +1 -1
- data/lib/straight.rb +10 -1
- data/lib/straight/blockchain_adapter.rb +2 -13
- data/lib/straight/blockchain_adapters/biteasy_adapter.rb +74 -0
- data/lib/straight/blockchain_adapters/blockchain_info_adapter.rb +37 -17
- data/lib/straight/blockchain_adapters/mycelium_adapter.rb +144 -0
- data/lib/straight/exchange_rate_adapter.rb +20 -1
- data/lib/straight/exchange_rate_adapters/average_rate_adapter.rb +54 -0
- data/lib/straight/exchange_rate_adapters/bitpay_adapter.rb +5 -2
- data/lib/straight/exchange_rate_adapters/bitstamp_adapter.rb +2 -1
- data/lib/straight/exchange_rate_adapters/btce_adapter.rb +18 -0
- data/lib/straight/exchange_rate_adapters/coinbase_adapter.rb +2 -4
- data/lib/straight/exchange_rate_adapters/kraken_adapter.rb +18 -0
- data/lib/straight/exchange_rate_adapters/localbitcoins_adapter.rb +17 -0
- data/lib/straight/exchange_rate_adapters/okcoin_adapter.rb +18 -0
- data/lib/straight/gateway.rb +20 -9
- data/lib/straight/order.rb +46 -13
- data/spec/lib/blockchain_adapters/{helloblock_io_spec.rb → biteasy_adapter_spec.rb} +23 -18
- data/spec/lib/blockchain_adapters/{blockchain_info_spec.rb → blockchain_info_adapter_spec.rb} +8 -3
- data/spec/lib/blockchain_adapters/mycelium_adapter_spec.rb +54 -0
- data/spec/lib/exchange_rate_adapter_spec.rb +6 -1
- data/spec/lib/exchange_rate_adapters/average_rate_adapter_spec.rb +43 -0
- data/spec/lib/exchange_rate_adapters/bitpay_adapter_spec.rb +14 -1
- data/spec/lib/exchange_rate_adapters/bitstamp_adapter_spec.rb +14 -1
- data/spec/lib/exchange_rate_adapters/btce_adapter_spec.rb +27 -0
- data/spec/lib/exchange_rate_adapters/coinbase_adapter_spec.rb +14 -1
- data/spec/lib/exchange_rate_adapters/kraken_adapter_spec.rb +27 -0
- data/spec/lib/exchange_rate_adapters/localbitcoins_adapter_spec.rb +27 -0
- data/spec/lib/exchange_rate_adapters/okcoin_adapter_spec.rb +27 -0
- data/spec/lib/gateway_spec.rb +23 -5
- data/spec/lib/order_spec.rb +18 -2
- data/straight.gemspec +95 -0
- metadata +33 -6
- data/lib/straight/blockchain_adapters/helloblock_io_adapter.rb +0 -53
| @@ -3,7 +3,8 @@ module Straight | |
| 3 3 |  | 
| 4 4 | 
             
                class Adapter
         | 
| 5 5 |  | 
| 6 | 
            -
                   | 
| 6 | 
            +
                  include Singleton
         | 
| 7 | 
            +
             | 
| 7 8 | 
             
                  class FetchingFailed       < Exception; end
         | 
| 8 9 | 
             
                  class CurrencyNotSupported < Exception; end
         | 
| 9 10 |  | 
| @@ -39,6 +40,24 @@ module Straight | |
| 39 40 | 
             
                    nil # this should be changed in descendant classes
         | 
| 40 41 | 
             
                  end
         | 
| 41 42 |  | 
| 43 | 
            +
                  # This method will get value we are interested in from hash and
         | 
| 44 | 
            +
                  # prevent failing with 'undefined method [] for Nil' if at some point hash doesn't have such key value pair
         | 
| 45 | 
            +
                  def get_rate_value_from_hash(rates_hash, *keys)
         | 
| 46 | 
            +
                    keys.inject(rates_hash) do |intermediate, key|
         | 
| 47 | 
            +
                      if intermediate.respond_to?(:[])
         | 
| 48 | 
            +
                        intermediate[key]
         | 
| 49 | 
            +
                      else
         | 
| 50 | 
            +
                        raise CurrencyNotSupported 
         | 
| 51 | 
            +
                      end
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  # We dont want to have false positive rate, because nil.to_f is 0.0
         | 
| 56 | 
            +
                  # This method checks that rate value is not nil
         | 
| 57 | 
            +
                  def rate_to_f(rate)
         | 
| 58 | 
            +
                    rate ? rate.to_f : raise(CurrencyNotSupported)
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 42 61 | 
             
                end
         | 
| 43 62 |  | 
| 44 63 | 
             
              end
         | 
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            module Straight
         | 
| 2 | 
            +
              module ExchangeRate
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class AverageRateAdapter < Adapter
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  # Takes exchange rate adapters instances or classes as arguments
         | 
| 7 | 
            +
                  def self.instance(*adapters)
         | 
| 8 | 
            +
                    instance = super()
         | 
| 9 | 
            +
                    instance.instance_variable_set(:@adapters, adapters.map { |adapter| adapter.respond_to?(:instance) ? adapter.instance : adapter })
         | 
| 10 | 
            +
                    instance
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def fetch_rates!
         | 
| 14 | 
            +
                    failed_fetches = 0
         | 
| 15 | 
            +
                    @adapters.each do |adapter|
         | 
| 16 | 
            +
                      begin
         | 
| 17 | 
            +
                        adapter.fetch_rates!
         | 
| 18 | 
            +
                      rescue Exception => e
         | 
| 19 | 
            +
                        failed_fetches += 1
         | 
| 20 | 
            +
                        raise e if failed_fetches == @adapters.size
         | 
| 21 | 
            +
                      end
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def rate_for(currency_code)
         | 
| 26 | 
            +
                    rates = []
         | 
| 27 | 
            +
                    @adapters.each do |adapter| 
         | 
| 28 | 
            +
                      begin
         | 
| 29 | 
            +
                        rates << adapter.rate_for(currency_code)
         | 
| 30 | 
            +
                      rescue CurrencyNotSupported
         | 
| 31 | 
            +
                        rates << nil
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    unless rates.select(&:nil?).size == @adapters.size 
         | 
| 36 | 
            +
                      rates.compact!
         | 
| 37 | 
            +
                      rates.inject {|sum, rate| sum + rate} / rates.size
         | 
| 38 | 
            +
                    else
         | 
| 39 | 
            +
                      raise CurrencyNotSupported
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def get_rate_value_from_hash(rates_hash, *keys)
         | 
| 44 | 
            +
                    raise "This method is not supposed to be used in #{self.class}."
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def rate_to_f(rate)
         | 
| 48 | 
            +
                    raise "This method is not supposed to be used in #{self.class}."
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
            end
         | 
| @@ -7,8 +7,11 @@ module Straight | |
| 7 7 |  | 
| 8 8 | 
             
                  def rate_for(currency_code)
         | 
| 9 9 | 
             
                    super
         | 
| 10 | 
            -
                    @rates.each do | | 
| 11 | 
            -
                       | 
| 10 | 
            +
                    @rates.each do |rt|
         | 
| 11 | 
            +
                      if rt['code'] == currency_code
         | 
| 12 | 
            +
                        rate = get_rate_value_from_hash(rt, 'rate')
         | 
| 13 | 
            +
                        return rate_to_f(rate)
         | 
| 14 | 
            +
                      end
         | 
| 12 15 | 
             
                    end
         | 
| 13 16 | 
             
                    raise CurrencyNotSupported 
         | 
| 14 17 | 
             
                  end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            module Straight
         | 
| 2 | 
            +
              module ExchangeRate
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class BtceAdapter < Adapter
         | 
| 5 | 
            +
                  
         | 
| 6 | 
            +
                  FETCH_URL = 'https://btc-e.com/api/2/btc_usd/ticker'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def rate_for(currency_code)
         | 
| 9 | 
            +
                    super
         | 
| 10 | 
            +
                    raise CurrencyNotSupported if !FETCH_URL.include?("btc_#{currency_code.downcase}")
         | 
| 11 | 
            +
                    rate = get_rate_value_from_hash(@rates, 'ticker', 'last')
         | 
| 12 | 
            +
                    rate_to_f(rate)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -7,10 +7,8 @@ module Straight | |
| 7 7 |  | 
| 8 8 | 
             
                  def rate_for(currency_code)
         | 
| 9 9 | 
             
                    super
         | 
| 10 | 
            -
                     | 
| 11 | 
            -
             | 
| 12 | 
            -
                    end
         | 
| 13 | 
            -
                    raise CurrencyNotSupported 
         | 
| 10 | 
            +
                    rate = get_rate_value_from_hash(@rates, "btc_to_#{currency_code.downcase}")
         | 
| 11 | 
            +
                    rate_to_f(rate)
         | 
| 14 12 | 
             
                  end
         | 
| 15 13 |  | 
| 16 14 | 
             
                end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            module Straight
         | 
| 2 | 
            +
              module ExchangeRate
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class KrakenAdapter < Adapter
         | 
| 5 | 
            +
                  
         | 
| 6 | 
            +
                  FETCH_URL = 'https://api.kraken.com/0/public/Ticker?pair=xbtusd'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def rate_for(currency_code)
         | 
| 9 | 
            +
                    super
         | 
| 10 | 
            +
                    rate = get_rate_value_from_hash(@rates, 'result', 'XXBTZ' + currency_code.upcase, 'c')
         | 
| 11 | 
            +
                    rate = rate.kind_of?(Array) ? rate.first : raise(CurrencyNotSupported)
         | 
| 12 | 
            +
                    rate_to_f(rate)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            module Straight
         | 
| 2 | 
            +
              module ExchangeRate
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class LocalbitcoinsAdapter < Adapter
         | 
| 5 | 
            +
                  
         | 
| 6 | 
            +
                  FETCH_URL = 'https://localbitcoins.com/bitcoinaverage/ticker-all-currencies/'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def rate_for(currency_code)
         | 
| 9 | 
            +
                    super
         | 
| 10 | 
            +
                    rate = get_rate_value_from_hash(@rates, currency_code.upcase, 'rates', 'last')
         | 
| 11 | 
            +
                    rate_to_f(rate)
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            module Straight
         | 
| 2 | 
            +
              module ExchangeRate
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class OkcoinAdapter < Adapter
         | 
| 5 | 
            +
                  
         | 
| 6 | 
            +
                  FETCH_URL = 'https://www.okcoin.com/api/ticker.do?ok=1'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def rate_for(currency_code)
         | 
| 9 | 
            +
                    super
         | 
| 10 | 
            +
                    raise CurrencyNotSupported if currency_code != 'USD'
         | 
| 11 | 
            +
                    rate = get_rate_value_from_hash(@rates, 'ticker', 'last')
         | 
| 12 | 
            +
                    rate_to_f(rate)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
    
        data/lib/straight/gateway.rb
    CHANGED
    
    | @@ -43,9 +43,8 @@ module Straight | |
| 43 43 |  | 
| 44 44 | 
             
                # Determines the algorithm for consequitive checks of the order status.
         | 
| 45 45 | 
             
                DEFAULT_STATUS_CHECK_SCHEDULE = -> (period, iteration_index) do
         | 
| 46 | 
            -
                  return false if period > 640
         | 
| 47 46 | 
             
                  iteration_index += 1
         | 
| 48 | 
            -
                  if iteration_index  | 
| 47 | 
            +
                  if iteration_index >= 20
         | 
| 49 48 | 
             
                    period          *= 2
         | 
| 50 49 | 
             
                    iteration_index  = 0
         | 
| 51 50 | 
             
                  end
         | 
| @@ -56,7 +55,7 @@ module Straight | |
| 56 55 | 
             
                # call super() somehwere inside those methods.
         | 
| 57 56 | 
             
                #
         | 
| 58 57 | 
             
                # In short, the idea is to let the class we're being prepended to do its magic
         | 
| 59 | 
            -
                # after  | 
| 58 | 
            +
                # after our methods are finished.
         | 
| 60 59 | 
             
                module Prependable
         | 
| 61 60 | 
             
                end
         | 
| 62 61 |  | 
| @@ -85,7 +84,8 @@ module Straight | |
| 85 84 | 
             
                  # the one a user of this class is going to properly increment) that is used to generate a
         | 
| 86 85 | 
             
                  # an BIP32 bitcoin address deterministically.
         | 
| 87 86 | 
             
                  def address_for_keychain_id(id)
         | 
| 88 | 
            -
                     | 
| 87 | 
            +
                    # The 'm/0/n' notation is used by both Electrum and Mycelium
         | 
| 88 | 
            +
                    keychain.node_for_path("m/0/#{id.to_s}").to_address
         | 
| 89 89 | 
             
                  end
         | 
| 90 90 |  | 
| 91 91 | 
             
                  def fetch_transaction(tid, address: nil)
         | 
| @@ -101,7 +101,7 @@ module Straight | |
| 101 101 | 
             
                  end
         | 
| 102 102 |  | 
| 103 103 | 
             
                  def keychain
         | 
| 104 | 
            -
                    @keychain ||= MoneyTree::Node.from_serialized_address( | 
| 104 | 
            +
                    @keychain ||= MoneyTree::Node.from_serialized_address(pubkey)
         | 
| 105 105 | 
             
                  end
         | 
| 106 106 |  | 
| 107 107 | 
             
                  # This is a callback method called from each order
         | 
| @@ -130,6 +130,13 @@ module Straight | |
| 130 130 | 
             
                    end
         | 
| 131 131 | 
             
                  end
         | 
| 132 132 |  | 
| 133 | 
            +
                  def current_exchange_rate(currency=self.default_currency)
         | 
| 134 | 
            +
                    currency = currency.to_s.upcase
         | 
| 135 | 
            +
                    try_adapters(@exchange_rate_adapters) do |a|
         | 
| 136 | 
            +
                      a.rate_for(currency)
         | 
| 137 | 
            +
                    end
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
             | 
| 133 140 | 
             
                  private
         | 
| 134 141 |  | 
| 135 142 | 
             
                    # Calls the block with each adapter until one of them does not fail.
         | 
| @@ -163,12 +170,16 @@ module Straight | |
| 163 170 | 
             
                  @default_currency = 'BTC'
         | 
| 164 171 | 
             
                  @blockchain_adapters = [
         | 
| 165 172 | 
             
                    Blockchain::BlockchainInfoAdapter.mainnet_adapter,
         | 
| 166 | 
            -
                    Blockchain:: | 
| 173 | 
            +
                    Blockchain::MyceliumAdapter.mainnet_adapter
         | 
| 167 174 | 
             
                  ]
         | 
| 168 175 | 
             
                  @exchange_rate_adapters = [
         | 
| 169 | 
            -
                    ExchangeRate::BitpayAdapter. | 
| 170 | 
            -
                    ExchangeRate::CoinbaseAdapter. | 
| 171 | 
            -
                    ExchangeRate::BitstampAdapter. | 
| 176 | 
            +
                    ExchangeRate::BitpayAdapter.instance, 
         | 
| 177 | 
            +
                    ExchangeRate::CoinbaseAdapter.instance,
         | 
| 178 | 
            +
                    ExchangeRate::BitstampAdapter.instance,
         | 
| 179 | 
            +
                    ExchangeRate::BtceAdapter.instance,
         | 
| 180 | 
            +
                    ExchangeRate::KrakenAdapter.instance,
         | 
| 181 | 
            +
                    ExchangeRate::LocalbitcoinsAdapter.instance,
         | 
| 182 | 
            +
                    ExchangeRate::OkcoinAdapter.instance
         | 
| 172 183 | 
             
                  ]
         | 
| 173 184 | 
             
                  @status_check_schedule = DEFAULT_STATUS_CHECK_SCHEDULE
         | 
| 174 185 | 
             
                end
         | 
    
        data/lib/straight/order.rb
    CHANGED
    
    | @@ -44,6 +44,8 @@ module Straight | |
| 44 44 | 
             
                  expired:      5  # too much time passed since creating an order
         | 
| 45 45 | 
             
                }
         | 
| 46 46 |  | 
| 47 | 
            +
                attr_reader :old_status
         | 
| 48 | 
            +
             | 
| 47 49 | 
             
                class IncorrectAmount < Exception; end
         | 
| 48 50 |  | 
| 49 51 | 
             
                # If you are defining methods in this module, it means you most likely want to
         | 
| @@ -61,7 +63,18 @@ module Straight | |
| 61 63 | 
             
                  # If as_sym is set to true, then each status is returned as Symbol, otherwise
         | 
| 62 64 | 
             
                  # an equivalent Integer from STATUSES is returned.
         | 
| 63 65 | 
             
                  def status(as_sym: false, reload: false)
         | 
| 64 | 
            -
             | 
| 66 | 
            +
             | 
| 67 | 
            +
                    if defined?(super)
         | 
| 68 | 
            +
                      begin 
         | 
| 69 | 
            +
                        @status = super
         | 
| 70 | 
            +
                      # if no method with arguments found in the class
         | 
| 71 | 
            +
                      # we're prepending to, then let's use a standard getter
         | 
| 72 | 
            +
                      # with no argument.
         | 
| 73 | 
            +
                      rescue ArgumentError
         | 
| 74 | 
            +
                        @status = super()
         | 
| 75 | 
            +
                      end
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
             | 
| 65 78 | 
             
                    # Prohibit status update if the order was paid in some way.
         | 
| 66 79 | 
             
                    # This is just a caching workaround so we don't query
         | 
| 67 80 | 
             
                    # the blockchain needlessly. The actual safety switch is in the setter.
         | 
| @@ -105,11 +118,16 @@ module Straight | |
| 105 118 | 
             
                    # The order in which these statements currently are prevents that error, because
         | 
| 106 119 | 
             
                    # by the time a callback checks the status it's already set.
         | 
| 107 120 | 
             
                    @status_changed = (@status != new_status)
         | 
| 121 | 
            +
                    @old_status     = @status
         | 
| 108 122 | 
             
                    @status         = new_status
         | 
| 109 | 
            -
                    gateway.order_status_changed(self) if  | 
| 123 | 
            +
                    gateway.order_status_changed(self) if status_changed?
         | 
| 110 124 | 
             
                    super if defined?(super)
         | 
| 111 125 | 
             
                  end
         | 
| 112 126 |  | 
| 127 | 
            +
                  def status_changed?
         | 
| 128 | 
            +
                    @status_changed
         | 
| 129 | 
            +
                  end
         | 
| 130 | 
            +
             | 
| 113 131 | 
             
                end
         | 
| 114 132 |  | 
| 115 133 | 
             
                module Includable
         | 
| @@ -144,22 +162,32 @@ module Straight | |
| 144 162 | 
             
                  #     order.start_periodic_status_check
         | 
| 145 163 | 
             
                  #   end
         | 
| 146 164 | 
             
                  #
         | 
| 147 | 
            -
                   | 
| 148 | 
            -
             | 
| 165 | 
            +
                  # `duration` argument (value is in seconds) allows you to
         | 
| 166 | 
            +
                  # control in what time an order expires. In other words, we
         | 
| 167 | 
            +
                  # keep checking for new transactions until the time passes.
         | 
| 168 | 
            +
                  # Then we stop and set Order's status to STATUS[:expired]. See
         | 
| 169 | 
            +
                  # #check_status_on_schedule for the implementation details.
         | 
| 170 | 
            +
                  def start_periodic_status_check(duration: 600)
         | 
| 171 | 
            +
                    check_status_on_schedule(duration: duration)
         | 
| 149 172 | 
             
                  end
         | 
| 150 173 |  | 
| 151 174 | 
             
                  # Recursion here! Keeps calling itself according to the schedule until
         | 
| 152 175 | 
             
                  # either the status changes or the schedule tells it to stop.
         | 
| 153 | 
            -
                  def check_status_on_schedule(period: 10, iteration_index: 0)
         | 
| 176 | 
            +
                  def check_status_on_schedule(period: 10, iteration_index: 0, duration: 600, time_passed: 0)
         | 
| 154 177 | 
             
                    self.status(reload: true)
         | 
| 155 | 
            -
                     | 
| 156 | 
            -
                    if  | 
| 157 | 
            -
                       | 
| 158 | 
            -
             | 
| 159 | 
            -
                        period | 
| 160 | 
            -
                         | 
| 161 | 
            -
                       | 
| 162 | 
            -
             | 
| 178 | 
            +
                    time_passed += period
         | 
| 179 | 
            +
                    if duration >= time_passed # Stop checking if status is >= 2
         | 
| 180 | 
            +
                      if self.status < 2
         | 
| 181 | 
            +
                        schedule = gateway.status_check_schedule.call(period, iteration_index)
         | 
| 182 | 
            +
                        sleep period
         | 
| 183 | 
            +
                        check_status_on_schedule(
         | 
| 184 | 
            +
                          period:          schedule[:period],
         | 
| 185 | 
            +
                          iteration_index: schedule[:iteration_index],
         | 
| 186 | 
            +
                          duration:        duration,
         | 
| 187 | 
            +
                          time_passed:     time_passed
         | 
| 188 | 
            +
                        )
         | 
| 189 | 
            +
                      end
         | 
| 190 | 
            +
                    elsif self.status < 2
         | 
| 163 191 | 
             
                      self.status = STATUSES[:expired]
         | 
| 164 192 | 
             
                    end
         | 
| 165 193 | 
             
                  end
         | 
| @@ -172,6 +200,11 @@ module Straight | |
| 172 200 | 
             
                    { status: status, amount: amount, address: address, tid: tid }
         | 
| 173 201 | 
             
                  end
         | 
| 174 202 |  | 
| 203 | 
            +
                  def amount_in_btc(as: :number)
         | 
| 204 | 
            +
                    a = Satoshi.new(amount, from_unit: :satoshi, to_unit: :btc)
         | 
| 205 | 
            +
                    as == :string ? a.to_unit(as: :string) : a.to_unit
         | 
| 206 | 
            +
                  end
         | 
| 207 | 
            +
             | 
| 175 208 | 
             
                end
         | 
| 176 209 |  | 
| 177 210 | 
             
              end
         | 
| @@ -1,14 +1,8 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 |  | 
| 3 | 
            -
            RSpec.describe Straight::Blockchain:: | 
| 3 | 
            +
            RSpec.describe Straight::Blockchain::BiteasyAdapter do
         | 
| 4 4 |  | 
| 5 | 
            -
              subject(:adapter) { Straight::Blockchain:: | 
| 6 | 
            -
             | 
| 7 | 
            -
              it "fetches all transactions for the current address" do
         | 
| 8 | 
            -
                address = "3B1QZ8FpAaHBgkSB5gFt76ag5AW9VeP8xp"
         | 
| 9 | 
            -
                expect(adapter).to receive(:straighten_transaction).with(anything, address: address).at_least(:once)
         | 
| 10 | 
            -
                expect(adapter.fetch_transactions_for(address)).not_to be_empty
         | 
| 11 | 
            -
              end
         | 
| 5 | 
            +
              subject(:adapter) { Straight::Blockchain::BiteasyAdapter.mainnet_adapter }
         | 
| 12 6 |  | 
| 13 7 | 
             
              it "fetches the balance for a given address" do
         | 
| 14 8 | 
             
                address = "3B1QZ8FpAaHBgkSB5gFt76ag5AW9VeP8xp"
         | 
| @@ -20,24 +14,35 @@ RSpec.describe Straight::Blockchain::HelloblockIoAdapter do | |
| 20 14 | 
             
                expect(adapter.fetch_transaction(tid)[:total_amount]).to eq(832947)
         | 
| 21 15 | 
             
              end
         | 
| 22 16 |  | 
| 23 | 
            -
              it " | 
| 24 | 
            -
                 | 
| 25 | 
            -
                expect(adapter. | 
| 17 | 
            +
              it "calculates total_amount of a transaction for the given address only" do
         | 
| 18 | 
            +
                t = { 'data' => {'outputs' => [{ 'value' => 1, 'to_address' => 'address1'}, { 'value' => 2, 'to_address' => 'address2'}] } }
         | 
| 19 | 
            +
                expect(adapter.send(:straighten_transaction, t, address: 'address1')[:total_amount]).to eq(1)
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              it "fetches all transactions for the current address" do
         | 
| 23 | 
            +
                address = "3B1QZ8FpAaHBgkSB5gFt76ag5AW9VeP8xp"
         | 
| 24 | 
            +
                expect(adapter).to receive(:straighten_transaction).with(anything, address: address).at_least(:once)
         | 
| 25 | 
            +
                expect(adapter.fetch_transactions_for(address)).not_to be_empty
         | 
| 26 26 | 
             
              end
         | 
| 27 27 |  | 
| 28 | 
            -
              it " | 
| 28 | 
            +
              it "calculates the number of confirmations for each transaction" do
         | 
| 29 29 | 
             
                tid = 'ae0d040f48d75fdc46d9035236a1782164857d6f0cca1f864640281115898560'
         | 
| 30 30 | 
             
                expect(adapter.fetch_transaction(tid)[:confirmations]).to be > 0
         | 
| 31 31 | 
             
              end
         | 
| 32 32 |  | 
| 33 | 
            -
              it " | 
| 34 | 
            -
                 | 
| 35 | 
            -
                expect( | 
| 33 | 
            +
              it "gets a transaction id among other data" do
         | 
| 34 | 
            +
                tid = 'ae0d040f48d75fdc46d9035236a1782164857d6f0cca1f864640281115898560'
         | 
| 35 | 
            +
                expect(adapter.fetch_transaction(tid)[:tid]).to eq(tid)
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              it "raises an exception when something goes wrong with fetching data" do
         | 
| 39 | 
            +
                expect( -> { adapter.send(:api_request, "/a-404-request") }).to raise_error(Straight::Blockchain::Adapter::RequestError)
         | 
| 36 40 | 
             
              end
         | 
| 37 41 |  | 
| 38 | 
            -
              it " | 
| 39 | 
            -
                 | 
| 40 | 
            -
                 | 
| 42 | 
            +
              it "uses the same Singleton instance" do
         | 
| 43 | 
            +
                a = Straight::Blockchain::BiteasyAdapter.mainnet_adapter
         | 
| 44 | 
            +
                b = Straight::Blockchain::BiteasyAdapter.mainnet_adapter
         | 
| 45 | 
            +
                expect(a).to eq(b)
         | 
| 41 46 | 
             
              end
         | 
| 42 47 |  | 
| 43 48 | 
             
            end
         | 
    
        data/spec/lib/blockchain_adapters/{blockchain_info_spec.rb → blockchain_info_adapter_spec.rb}
    RENAMED
    
    | @@ -31,7 +31,7 @@ RSpec.describe Straight::Blockchain::BlockchainInfoAdapter do | |
| 31 31 | 
             
              end
         | 
| 32 32 |  | 
| 33 33 | 
             
              it "caches blockchain.info latestblock requests" do
         | 
| 34 | 
            -
                expect(adapter).to receive(: | 
| 34 | 
            +
                expect(adapter).to receive(:api_request).once.and_return('{ "height": 1 }')
         | 
| 35 35 | 
             
                adapter.send(:calculate_confirmations, { "block_height" => 1 }, force_latest_block_reload: true)
         | 
| 36 36 | 
             
                adapter.send(:calculate_confirmations, { "block_height" => 1 })
         | 
| 37 37 | 
             
                adapter.send(:calculate_confirmations, { "block_height" => 1 })
         | 
| @@ -40,8 +40,7 @@ RSpec.describe Straight::Blockchain::BlockchainInfoAdapter do | |
| 40 40 | 
             
              end
         | 
| 41 41 |  | 
| 42 42 | 
             
              it "raises an exception when something goes wrong with fetching datd" do
         | 
| 43 | 
            -
                 | 
| 44 | 
            -
                expect( -> { adapter.http_request("http://blockchain.info/a-timed-out-request") }).to raise_error(Straight::Blockchain::Adapter::RequestError)
         | 
| 43 | 
            +
                expect( -> { adapter.send(:api_request, "/a-404-request") }).to raise_error(Straight::Blockchain::Adapter::RequestError)
         | 
| 45 44 | 
             
              end
         | 
| 46 45 |  | 
| 47 46 | 
             
              it "calculates total_amount of a transaction for the given address only" do
         | 
| @@ -49,4 +48,10 @@ RSpec.describe Straight::Blockchain::BlockchainInfoAdapter do | |
| 49 48 | 
             
                expect(adapter.send(:straighten_transaction, t, address: 'address1')[:total_amount]).to eq(1)
         | 
| 50 49 | 
             
              end
         | 
| 51 50 |  | 
| 51 | 
            +
              it "uses the same Singleton instance" do
         | 
| 52 | 
            +
                a = Straight::Blockchain::BlockchainInfoAdapter.mainnet_adapter
         | 
| 53 | 
            +
                b = Straight::Blockchain::BlockchainInfoAdapter.mainnet_adapter
         | 
| 54 | 
            +
                expect(a).to eq(b)
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 52 57 | 
             
            end
         |