# frozen_string_literal: true require 'faraday' require 'json' # @author Hernani Rodrigues Vaz module Etht # class Config class Config attr_accessor :key, :secret, :url, :raise_exceptions attr_writer :logger def logger @logger ||= Logger.new(STDERR) end end # class Api class Api attr_reader :connection def initialize(params = {}) @connection = Etht::Client.new(params) end def get(params) response = connection.get(params) response['result'] end end # class Client class Client URL = 'https://api.etherscan.io/' HEADERS = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }.freeze attr_reader :key, :url, :user_agent, :headers, :raise_exceptions attr_writer :adapter, :conn def initialize(params = {}) @key = params.fetch(:key, Etht.config.key) @url = params.fetch(:url, Etht.config.url || URL) @adapter = params.fetch(:adapter, adapter) @conn = params.fetch(:conn, conn) @user_agent = params.fetch(:user_agent, "etherscan/#{Etht::VERSION};ruby") @headers = HEADERS.merge('User-Agent' => @user_agent) @raise_exceptions = params.fetch(:raise_exceptions, Etht.config.raise_exceptions || true) yield self if block_given? end def get(params = {}) endpoint = 'api' merged_params = params.merge({ apikey: key }) response = conn.get(endpoint) do |req| req.headers = headers req.params = merged_params end raise Etht::Exception, response if raise_exceptions? && response.status != 200 JSON(response.body) end def conn @conn ||= Faraday.new(url: @url) do |conn| conn.request :url_encoded conn.adapter adapter end end def adapter @adapter ||= Faraday.default_adapter end def raise_exceptions? @raise_exceptions end end # class Accounts < Etht::Api class Accounts < Api def address_balance(address) params = { module: 'account', action: 'balance', address: address, tag: 'latest' } get(params) end def multi_address_balance(addresses) raise Etht::Exception, 'up to 20 accounts in a single batch' if addresses.size > 20 params = { module: 'account', action: 'balancemulti', address: addresses.join(','), tag: 'latest' } get(params) end # Get a list of 'Normal' Transactions By Address # if page is not defined Returns up to 10000 last transactions # Available args: start_block, end_block, sort, page, offset # @param sort 'asc' -> ascending order, 'des' -> descending order # @param start_block starting blockNo to retrieve results # @param end_block ending blockNo to retrieve results # @param page Paginated result # @param offset max records to return def normal_transactions(address, args = {}) action = 'txlist' transcations(action, address, nil, args) end # Get a list of 'Internal' Transactions By Address # Available args: start_block, end_block, sort, page, offset def internal_transactions(address, args = {}) action = 'txlistinternal' transcations(action, address, nil, args) end # Get a list of "ERC20 - Token Transfer Events" # @param contract_address Token address (set nil to get a list of all ERC20 transactions) # @param address Address for ERC20 transactions (optional) # Available args: start_block, end_block, sort, page, offset def token_transactions(contract_address, address = nil, args = {}) raise Etht::Exception, 'contract or address must be defined' if (contract_address || address).nil? action = 'tokentx' transcations(action, address, contract_address, args) end private def transcations(action, address, contract_address, args) params = { module: 'account', action: action, address: address, contractaddress: contract_address, startblock: args[:start_block], endblock: args[:end_block], page: args[:page], offset: args[:offset], sort: args[:sort] }.reject { |_, v| v.nil? } get(params) end end # class Tokens < Etht::Api class Tokens < Api def total_supply(contract_address) params = { module: 'stats', action: 'tokensupply', contractaddress: contract_address } get(params) end def balance(address, contract_address) params = { module: 'account', action: 'tokenbalance', address: address, contractaddress: contract_address } get(params) end end def self.configure yield config end def self.config @config ||= Config.new end def self.logger config.logger end configure do |config| config.key = ENV['ETHERSCAN_API_KEY'] end end