class Straight::Blockchain::MyceliumAdapter

Constants

MAINNET_SERVERS
PINNED_CERTIFICATES
TESTNET_SERVERS

Public Class Methods

mainnet_adapter() click to toggle source
# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 18
def self.mainnet_adapter
  new(testnet: false)
end
new(testnet: false) click to toggle source
# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 26
def initialize(testnet: false)
  @latest_block = { cache_timestamp: nil, block: nil }
  @testnet = testnet
  @api_servers = @testnet ? TESTNET_SERVERS : MAINNET_SERVERS
  set_base_url
end
testnet_adapter() click to toggle source
# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 22
def self.testnet_adapter
  new(testnet: true)
end

Public Instance Methods

fetch_balance_for(address) click to toggle source

Returns the current balance of the address

# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 69
def fetch_balance_for(address)
  unspent = 0
  api_request('queryUnspentOutputs', { addresses: [address]})['unspent'].each do |out|
    unspent += out['value']
  end
  unspent
end
fetch_transaction(tid, address: nil) click to toggle source

Returns transaction info for the tid

# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 49
def fetch_transaction(tid, address: nil)
  transaction = api_request('getTransactions', { txIds: [tid] })['transactions'].first
  straighten_transaction transaction, address: address
end
fetch_transactions_for(address) click to toggle source

Supposed to returns all transactions for the address, but currently actually returns the first one, since we only need one.

# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 56
def fetch_transactions_for(address)
  # API may return nil instead of an empty array if address turns out to be invalid
  # (for example when trying to supply a testnet address instead of mainnet while using
  # mainnet adapter.
  if api_response = api_request('queryTransactionInventory', { addresses: [address], limit: 1 })
    tid = api_response["txIds"].first
    tid ? [fetch_transaction(tid, address: address)] : []
  else
    raise BitcoinAddressInvalid, message: "address in question: #{address}"
  end
end
latest_block(force_reload: false) click to toggle source
# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 77
def latest_block(force_reload: false)
  # If we checked Blockchain.info latest block data
  # more than a minute ago, check again. Otherwise, use cached version.
  if @latest_block[:cache_timestamp].nil?              ||
     @latest_block[:cache_timestamp] < (Time.now - 60) ||
     force_reload
    @latest_block = {
      cache_timestamp: Time.now,
      block: api_request('queryUnspentOutputs', { addresses: []} )
    }
  else
    @latest_block
  end
end
next_server() click to toggle source
# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 44
def next_server
  set_base_url(@api_servers.index(@base_url) + 1)
end
set_base_url(num = 0) click to toggle source

Set url for API request. @param num [Integer] a number of server in array

# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 39
def set_base_url(num = 0)
  return nil if num >= @api_servers.size
  @base_url = @api_servers[num]
end
testnet?() click to toggle source
# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 33
def testnet?
  @testnet
end

Private Instance Methods

api_request(method, params={}) click to toggle source
# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 94
def api_request(method, params={})
  ssl_opts =
    if testnet?
      {verify: false}
    else
      {verify_callback: lambda { |preverify_ok, store_context|
        end_cert = store_context.chain[0] # pinned invalid certificate
        PINNED_CERTIFICATES.include?(OpenSSL::Digest::SHA256.hexdigest(end_cert.to_der)) || preverify_ok
      }}
    end
  begin
    conn = Faraday.new(url: "#{@base_url}/#{method}", ssl: ssl_opts) do |faraday|
      faraday.request  :url_encoded             # form-encode POST params
      faraday.adapter  Faraday.default_adapter  # make requests with Net::HTTP
    end
    result = conn.post do |req|
      req.body = params.merge({version: 1}).to_json
      req.headers['Content-Type'] = 'application/json'
    end
    JSON.parse(result.body || '')['r']
  rescue => e
    next_server ? retry : raise(RequestError, YAML::dump(e))
  end
end
calculate_confirmations(block_height, force_latest_block_reload: false) click to toggle source

When we call calculate_confirmations, it doesn't always make a new request to the blockchain API. Instead, it checks if cached_id matches the one in the hash. It's useful when we want to calculate confirmations for all transactions for a certain address without making any new requests to the Blockchain API.

# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 154
def calculate_confirmations(block_height, force_latest_block_reload: false)

  if block_height && block_height != -1
    latest_block(force_reload: force_latest_block_reload)[:block]["height"] - block_height + 1
  else
    0
  end

end
straighten_transaction(transaction, address: nil) click to toggle source

Converts transaction info received from the source into the unified format expected by users of BlockchainAdapter instances.

# File lib/straight/blockchain_adapters/mycelium_adapter.rb, line 121
def straighten_transaction(transaction, address: nil)
  # Get the block number this transaction was included into
  block_height = transaction['height']
  tid          = transaction['txid']

  # Converting from Base64 to binary
  transaction = transaction['binary'].unpack('m0')[0]

  # Decoding
  transaction = BTC::Transaction.new(data: transaction)

  outs         = []
  total_amount = 0

  transaction.outputs.each do |out|
    amount = out.value
    receiving_address = out.script.standard_address
    total_amount += amount if address.nil? || address == receiving_address.to_s
    outs << {amount: amount, receiving_address: receiving_address}
  end

  {
    tid:           tid,
    total_amount:  total_amount.to_i,
    confirmations: calculate_confirmations(block_height),
    outs:          outs
  }
end