class Peatio::Infura::Blockchain

Constants

DEFAULT_FEATURES
FAILED
SUCCESS
TOKEN_EVENT_IDENTIFIER
UndefinedCurrencyError

Public Class Methods

new(custom_features = {}) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 14
def initialize(custom_features = {})
  @features = DEFAULT_FEATURES.merge(custom_features).slice(*SUPPORTED_FEATURES)
  @settings = {}
end

Public Instance Methods

configure(settings = {}) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 19
def configure(settings = {})
  # Clean client state during configure.
  @client = nil
  @erc20 = []; @eth = []

  @settings.merge!(settings.slice(*SUPPORTED_SETTINGS))
  @settings[:currencies]&.each do |c|
    if c.dig(:options, :erc20_contract_address).present?
      @erc20 << c
    else
      @eth << c
    end
  end
end
fetch_block!(block_number) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 34
def fetch_block!(block_number)
  block_json = client.json_rpc(:eth_getBlockByNumber, ["0x#{block_number.to_s(16)}", true])

  if block_json.blank? || block_json['transactions'].blank?
    return Peatio::Block.new(block_number, [])
  end
  block_json.fetch('transactions').each_with_object([]) do |tx, block_arr|
    if tx.fetch('input').hex <= 0
      next if invalid_eth_transaction?(tx)
    else
      next if @erc20.find { |c| c.dig(:options, :erc20_contract_address) == normalize_address(tx.fetch('to')) }.blank?
      tx = client.json_rpc(:eth_getTransactionReceipt, [normalize_txid(tx.fetch('hash'))])
      next if tx.nil? || tx.fetch('to').blank?
    end

    txs = build_transactions(tx).map do |ntx|
      Peatio::Transaction.new(ntx)
    end

    block_arr.append(*txs)
  end.yield_self { |block_arr| Peatio::Block.new(block_number, block_arr) }
rescue Infura::Client::Error => e
  raise Peatio::Blockchain::ClientError, e
end
fetch_transaction(transaction) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 81
def fetch_transaction(transaction)
  currency = settings[:currencies].find { |c| c.fetch(:id) == transaction.currency_id }
  return if currency.blank?
  txn_receipt = client.json_rpc(:eth_getTransactionReceipt, [transaction.hash])
  if currency.in?(@eth)
    txn_json = client.json_rpc(:eth_getTransactionByHash, [transaction.hash])
    attributes = {
      amount: convert_from_base_unit(txn_json.fetch('value').hex, currency),
      to_address: normalize_address(txn_json['to']),
      status: transaction_status(txn_receipt)
    }
  else
    txn_json = txn_receipt.fetch('logs').find { |log| log['logIndex'].to_i(16) == transaction.txout }
    attributes = {
      amount: convert_from_base_unit(txn_json.fetch('data').hex, currency),
      to_address: normalize_address('0x' + txn_json.fetch('topics').last[-40..-1]),
      status: transaction_status(txn_receipt)
    }
  end
  transaction.assign_attributes(attributes)
  transaction
end
latest_block_number() click to toggle source
# File lib/peatio/infura/blockchain.rb, line 59
def latest_block_number
  client.json_rpc(:eth_blockNumber).to_i(16)
rescue Infura::Client::Error => e
  raise Peatio::Blockchain::ClientError, e
end
load_balance_of_address!(address, currency_id) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 65
def load_balance_of_address!(address, currency_id)
  currency = settings[:currencies].find { |c| c[:id] == currency_id.to_s }
  raise UndefinedCurrencyError unless currency

  if currency.dig(:options, :erc20_contract_address).present?
    load_erc20_balance(address, currency)
  else
    client.json_rpc(:eth_getBalance, [normalize_address(address), 'latest'])
          .hex
          .to_d
          .yield_self { |amount| convert_from_base_unit(amount, currency) }
  end
rescue Infura::Client::Error => e
  raise Peatio::Blockchain::ClientError, e
end

Private Instance Methods

abi_encode(method, *args) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 209
def abi_encode(method, *args)
  '0x' + args.each_with_object(Digest::SHA3.hexdigest(method, 256)[0...8]) do |arg, data|
    data.concat(arg.gsub(/\A0x/, '').rjust(64, '0'))
  end
end
build_erc20_transactions(txn_receipt) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 150
def build_erc20_transactions(txn_receipt)
  # Build invalid transaction for failed withdrawals
  if transaction_status(txn_receipt) == 'fail' && txn_receipt.fetch('logs').blank?
    return build_invalid_erc20_transaction(txn_receipt)
  end

  txn_receipt.fetch('logs').each_with_object([]) do |log, formatted_txs|

    next if log.fetch('topics').blank? || log.fetch('topics')[0] != TOKEN_EVENT_IDENTIFIER

    # Skip if ERC20 contract address doesn't match.
    currencies = @erc20.select { |c| c.dig(:options, :erc20_contract_address) == log.fetch('address') }
    next if currencies.blank?

    destination_address = normalize_address('0x' + log.fetch('topics').last[-40..-1])

    currencies.each do |currency|
      formatted_txs << { hash:         normalize_txid(txn_receipt.fetch('transactionHash')),
                         amount:       convert_from_base_unit(log.fetch('data').hex, currency),
                         to_address:   destination_address,
                         txout:        log['logIndex'].to_i(16),
                         block_number: txn_receipt.fetch('blockNumber').to_i(16),
                         currency_id:  currency.fetch(:id),
                         status:       transaction_status(txn_receipt) }
    end
  end
end
build_eth_transactions(block_txn) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 138
def build_eth_transactions(block_txn)
  @eth.map do |currency|
    { hash:          normalize_txid(block_txn.fetch('hash')),
      amount:        convert_from_base_unit(block_txn.fetch('value').hex, currency),
      to_address:    normalize_address(block_txn['to']),
      txout:         block_txn.fetch('transactionIndex').to_i(16),
      block_number:  block_txn.fetch('blockNumber').to_i(16),
      currency_id:   currency.fetch(:id),
      status:        transaction_status(block_txn) }
  end
end
build_invalid_erc20_transaction(txn_receipt) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 178
def build_invalid_erc20_transaction(txn_receipt)
  currencies = @erc20.select { |c| c.dig(:options, :erc20_contract_address) == txn_receipt.fetch('to') }
  return if currencies.blank?

  currencies.each_with_object([]) do |currency, invalid_txs|
    invalid_txs << { hash:         normalize_txid(txn_receipt.fetch('transactionHash')),
                     block_number: txn_receipt.fetch('blockNumber').to_i(16),
                     currency_id:  currency.fetch(:id),
                     status:       transaction_status(txn_receipt) }
  end
end
build_transactions(tx_hash) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 130
def build_transactions(tx_hash)
  if tx_hash.has_key?('logs')
    build_erc20_transactions(tx_hash)
  else
    build_eth_transactions(tx_hash)
  end
end
client() click to toggle source
# File lib/peatio/infura/blockchain.rb, line 114
def client
  @client ||= Infura::Client.new(settings_fetch(:server))
end
contract_address(currency) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 205
def contract_address(currency)
  normalize_address(currency.dig(:options, :erc20_contract_address))
end
convert_from_base_unit(value, currency) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 215
def convert_from_base_unit(value, currency)
  value.to_d / currency.fetch(:base_factor).to_d
end
invalid_eth_transaction?(block_txn) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 200
def invalid_eth_transaction?(block_txn)
  block_txn.fetch('to').blank? \
  || block_txn.fetch('value').hex.to_d <= 0 && block_txn.fetch('input').hex <= 0
end
load_erc20_balance(address, currency) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 106
def load_erc20_balance(address, currency)
  data = abi_encode('balanceOf(address)', normalize_address(address))
  client.json_rpc(:eth_call, [{ to: contract_address(currency), data: data }, 'latest'])
        .hex
        .to_d
        .yield_self { |amount| convert_from_base_unit(amount, currency) }
end
normalize_address(address) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 126
def normalize_address(address)
  address.try(:downcase)
end
normalize_txid(txid) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 122
def normalize_txid(txid)
  txid.try(:downcase)
end
settings_fetch(key) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 118
def settings_fetch(key)
  @settings.fetch(key) { raise Peatio::Blockchain::MissingSettingError, key.to_s }
end
transaction_status(block_txn) click to toggle source
# File lib/peatio/infura/blockchain.rb, line 190
def transaction_status(block_txn)
  if block_txn.dig('status') == SUCCESS
    'success'
  elsif block_txn.dig('status') == FAILED
    'failed'
  else
    'pending'
  end
end