module CryptocoinPayable module Adapters class Bitcoin < Base # Satoshi in Bitcoin def self.subunit_in_main 100_000_000 end def self.coin_symbol 'BTC' end def fetch_transactions(address) fetch_block_explorer_transactions(address) rescue StandardError fetch_block_cypher_transactions(address) end def create_address(id) super.to_address(network: network) end private def prefix CryptocoinPayable.configuration.testnet ? 'testnet.' : '' end def network CryptocoinPayable.configuration.testnet ? :bitcoin_testnet : :bitcoin end def parse_total_tx_value_block_explorer(output_transactions, address) output_transactions .select { |out| out['scriptPubKey']['addresses'].try('include?', address) } .sum { |out| (out['value'].to_f * self.class.subunit_in_main).to_i } end def fetch_block_explorer_transactions(address) url = "https://#{prefix}blockexplorer.com/api/txs/?address=#{address}" parse_block_explorer_transactions(get_request(url).body, address) end def parse_block_explorer_transactions(response, address) json = JSON.parse(response) json['txs'].map { |tx| convert_block_explorer_transactions(tx, address) } rescue JSON::ParserError raise ApiError, response end def convert_block_explorer_transactions(transaction, address) { tx_hash: transaction['txid'], block_hash: transaction['blockhash'], block_time: transaction['blocktime'].nil? ? nil : parse_timestamp(transaction['blocktime']), estimated_tx_time: parse_timestamp(transaction['time']), estimated_tx_value: parse_total_tx_value_block_explorer(transaction['vout'], address), confirmations: transaction['confirmations'] } end def parse_total_tx_value_block_cypher(output_transactions, address) output_transactions .sum { |out| out['addresses'].join.eql?(address) ? out['value'] : 0 } end def fetch_block_cypher_transactions(address) url = "https://api.blockcypher.com/v1/btc/main/addrs/#{address}/full" parse_block_cypher_transactions(get_request(url).body, address) end def parse_block_cypher_transactions(response, address) json = JSON.parse(response) raise ApiError, json['error'] if json['error'] json['txs'].map { |tx| convert_block_cypher_transactions(tx, address) } rescue JSON::ParserError raise ApiError, response end def convert_block_cypher_transactions(transaction, address) { tx_hash: transaction['hash'], block_hash: transaction['block_hash'], block_time: parse_time(transaction['confirmed']), estimated_tx_time: parse_time(transaction['received']), estimated_tx_value: parse_total_tx_value_block_cypher(transaction['outputs'], address), confirmations: transaction['confirmations'].to_i } end end end end