etht 0.1.3 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd40aa4cd395e43e84e1e966de134790d849c95dd9ce5aae4214a88b8dca9d57
4
- data.tar.gz: 435af034f97a849f667635b38fc1fff7fb8cad63fe9bfd84ecce6d0a55a773a2
3
+ metadata.gz: 251284e1797817117272d9df42dc49b7a13deae5107445ca0488bd953d445b0f
4
+ data.tar.gz: 3f63d4fd81f939242e9cb5cd0543f26c515a1621c65a8704995fceaccfbfadc2
5
5
  SHA512:
6
- metadata.gz: 11fcca99b528324db5782efc2ad69c46cb4b3d458e746113ed76182bd48d7dac186675b8b5c7be26590a2feeaaa4c6aae164ccf1d8492a94ae5dc6557a52187c
7
- data.tar.gz: d0bf1915245f76f7a1c147789d7efea25fb3e98593892fe776044880e0c9849b3ee3b0a6c8830bb25fe0c7822d7c1f78aee28d65c53990852813373ba364b547
6
+ metadata.gz: 88873cd75be80f1b78cb760a4e643a394a16ff5fb01ee0328273e5684e19944ba7c5f344f3fd63488e009a0c2d79976bd7d0994b994062b9cf63f1c6c990f4bf
7
+ data.tar.gz: b3991969370592a9ecfc8e969f7b4205b475aae29aca5029e1c005217416e1380a1a5bed8cc5c69e4efd5612332791b8eb3376a311e3d02bba6e891810a2320a
@@ -0,0 +1,12 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.7
3
+ EnabledByDefault: true
4
+
5
+ Style/Copyright:
6
+ Enabled: false
7
+
8
+ Lint/ConstantResolution:
9
+ Enabled: false
10
+
11
+ Style/ConstantVisibility:
12
+ Enabled: false
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- etht (0.1.3)
5
- etherscan_api
4
+ etht (0.1.8)
5
+ faraday
6
6
  google-cloud-bigquery
7
+ json
7
8
  thor
8
9
  yard
9
10
 
@@ -15,9 +16,6 @@ GEM
15
16
  concurrent-ruby (1.1.7)
16
17
  declarative (0.0.20)
17
18
  declarative-option (0.1.0)
18
- etherscan_api (0.4.0)
19
- faraday
20
- json (~> 2.1)
21
19
  faraday (1.0.1)
22
20
  multipart-post (>= 1.2, < 3)
23
21
  google-api-client (0.43.0)
@@ -49,7 +47,7 @@ GEM
49
47
  signet (~> 0.14)
50
48
  httpclient (2.8.3)
51
49
  json (2.3.1)
52
- jwt (2.2.1)
50
+ jwt (2.2.2)
53
51
  memoist (0.16.2)
54
52
  mini_mime (1.0.2)
55
53
  multi_json (1.15.0)
data/README.md CHANGED
@@ -1,26 +1,16 @@
1
1
  # Etht
2
2
 
3
- Arquiva eth-transactions no bigquery. Pode apagar movimentos existentes ja no bigquery.
3
+ Arquiva eth-transactions no bigquery. Pode ajustar dias para reposicionamento temporal.
4
4
 
5
5
  ## Installation
6
6
 
7
- Add this line to your application's Gemfile:
8
-
9
- ```ruby
10
- gem 'etht'
11
- ```
12
-
13
- And then execute:
14
-
15
- $ bundle install
16
-
17
- Or install it yourself as:
18
-
19
7
  $ gem install etht
20
8
 
21
9
  ## Usage
22
10
 
23
- TODO: Write usage instructions here
11
+ $ etht help [COMMAND] # Describe available commands or one specific command
12
+ $ etht show # mostra carteiras e dados bigquery & etherscan
13
+ $ etht work # carrega dados novos do etherscan no bigquery
24
14
 
25
15
  ## Development
26
16
 
@@ -30,7 +20,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
30
20
 
31
21
  ## Contributing
32
22
 
33
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/etht. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/etht/blob/master/CODE_OF_CONDUCT.md).
23
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/etht. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/hernanirvaz/etht/blob/master/CODE_OF_CONDUCT.md).
34
24
 
35
25
 
36
26
  ## License
@@ -39,4 +29,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
39
29
 
40
30
  ## Code of Conduct
41
31
 
42
- Everyone interacting in the Etht project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/etht/blob/master/CODE_OF_CONDUCT.md).
32
+ Everyone interacting in the Etht project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/hernanirvaz/etht/blob/master/CODE_OF_CONDUCT.md).
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lib/etht/version'
3
+ require_relative('lib/etht/version')
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'etht'
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.email = ['hernanirvaz@gmail.com']
10
10
 
11
11
  spec.summary = 'Arquiva eth-transactions no bigquery.'
12
- spec.description = spec.summary + ' Pode apagar movimentos existentes ja no bigquery.'
12
+ spec.description = "#{spec.summary} Pode ajustar dias para reposicionamento temporal."
13
13
 
14
14
  spec.homepage = 'https://github.com/hernanirvaz/etht'
15
15
  spec.license = 'MIT'
@@ -20,18 +20,20 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  # Specify which files should be added to the gem when it is released.
22
22
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
- end
23
+ spec.files =
24
+ Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ end
26
27
  spec.bindir = 'exe'
27
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
29
  spec.require_paths = ['lib']
29
30
 
30
- spec.add_development_dependency 'bundler'
31
- spec.add_development_dependency 'rake'
31
+ spec.add_development_dependency('bundler')
32
+ spec.add_development_dependency('rake')
32
33
 
33
- spec.add_dependency 'etherscan_api'
34
- spec.add_dependency 'google-cloud-bigquery'
35
- spec.add_dependency 'thor'
36
- spec.add_dependency 'yard'
34
+ spec.add_dependency('faraday')
35
+ spec.add_dependency('google-cloud-bigquery')
36
+ spec.add_dependency('json')
37
+ spec.add_dependency('thor')
38
+ spec.add_dependency('yard')
37
39
  end
data/exe/etht CHANGED
@@ -1,3 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "etht"
4
+ require 'etht'
5
+
6
+ Etht::CLI.start(ARGV)
@@ -1,29 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thor'
4
- require 'etht/bigquery'
5
- require 'etht/version'
3
+ require('thor')
4
+ require('etht/bigquery')
5
+ require('etht/carteiras')
6
+ require('etht/etherscan')
7
+ require('etht/formatar')
8
+ require('etht/version')
6
9
 
7
10
  # @author Hernani Rodrigues Vaz
8
11
  module Etht
9
- ID = `whoami`.chomp
10
-
11
- class Error < StandardError; end
12
+ # classe para erros desta gem
13
+ class Erro < StandardError
14
+ # @return [StandardError] personalizacao dos erros
15
+ def initialize(msg)
16
+ super(msg)
17
+ end
18
+ end
12
19
 
13
- # CLI para carregar etherscan comuns no bigquery
20
+ # classe para carregar/mostrar dados comuns bigquery & etherscan
14
21
  class CLI < Thor
15
- desc 'work', 'carrega/apaga dados do etherscan'
16
- option :e, type: :boolean, default: false, desc: 'apaga linha igual'
17
- option :m, type: :boolean, default: false, desc: 'apaga linhas existencia multipla'
18
- # processa etherscan
22
+ desc 'work', 'carrega transacoes novas no bigquery'
23
+ option :h, type: :hash, default: {}, desc: 'configuracao ajuste reposicionamento temporal'
24
+ # carrega transacoes novas no bigquery
19
25
  def work
20
- Bigquery.new({ e: options[:e], m: options[:m], i: true }).processa_etherscan
26
+ Bigquery.new(options).processa
21
27
  end
22
28
 
23
- desc 'show', 'mostra dados do etherscan'
24
- # show etherscan
29
+ desc 'show', 'mostra reumo carteiras & transacoes'
30
+ option :v, type: :boolean, default: false, desc: 'mostra transacoes normais & token'
31
+ option :t, type: :boolean, default: false, desc: 'mostra transacoes todas ou somente novas'
32
+ # mostra reumo carteiras & transacoes
25
33
  def show
26
- Bigquery.new.processa_etherscan
34
+ Bigquery.new(options).carteiras.mostra_resumo
27
35
  end
28
36
 
29
37
  default_task :show
@@ -1,105 +1,159 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'etherscan'
4
- require 'google/cloud/bigquery'
5
-
6
- # config/initializers/etherscan.rb
7
- Etherscan.configure do |config|
8
- config.key = 'QA14GAMNM3AJIX8IEGA8MHEHBHJDTDSBPX'
9
- end
3
+ require('google/cloud/bigquery')
4
+ require('bigdecimal/util')
10
5
 
6
+ # @author Hernani Rodrigues Vaz
11
7
  module Etht
12
- DF = '%Y-%m-%d'
13
- DI = '%Y-%m-%d %H:%M:%S'
8
+ BD = 'hernanirvaz.coins'
14
9
 
15
- # (see Bigquery)
10
+ # classe para processar etherscan & bigquery
16
11
  class Bigquery
17
12
  # @return [Google::Cloud::Bigquery] API bigquery
18
- attr_reader :apibq
19
- # @return [Roo::CSV] folha calculo a processar
20
- attr_reader :folha
21
- # @return [Hash<Symbol, Boolean>] opcoes trabalho com linhas
22
- attr_reader :linha
23
-
24
- # @return [Array] row folha calculo em processamento
25
- attr_reader :row
13
+ attr_reader :api
26
14
  # @return [Google::Cloud::Bigquery::QueryJob] job bigquery
27
15
  attr_reader :job
28
- # @return (see sql_select)
29
- attr_reader :sql
30
-
31
- # @param [String] csv folha calculo para processar
32
- # @param [Hash<Symbol, Boolean>] ops opcoes trabalho com linhas
33
- # @option ops [Boolean] :e (false) apaga linha igual?
34
- # @option ops [Boolean] :m (false) apaga linhas existencia multipla?
35
- # @option ops [Boolean] :i (false) insere linha nova?
36
- # @return [Bigquery] acesso folhas calculo bloks.io & correspondente bigquery dataset
37
- def initialize(csv = '', ops = { e: false, m: false, i: false })
16
+ # @return [Thor::CoreExt::HashWithIndifferentAccess] opcoes trabalho
17
+ attr_reader :ops
18
+
19
+ # @param [Thor::CoreExt::HashWithIndifferentAccess] pop opcoes trabalho
20
+ # @option pop [Hash] :h ({}) configuracao ajuste reposicionamento temporal
21
+ # @option pop [Boolean] :v (false) mostra transacoes normais & token?
22
+ # @option pop [Boolean] :t (false) mostra transacoes todas ou somente novas?
23
+ # @return [Bigquery] API bigquery & API etherscan
24
+ def initialize(pop)
38
25
  # usa env GOOGLE_APPLICATION_CREDENTIALS para obter credentials
39
26
  # @see https://cloud.google.com/bigquery/docs/authentication/getting-started
40
- @apibq = Google::Cloud::Bigquery.new
41
- @folha = Roo::CSV.new(csv) if csv.size.positive?
42
- @linha = ops
27
+ @api = Google::Cloud::Bigquery.new
28
+ @ops = pop
43
29
  end
44
30
 
45
- # cria job bigquery & verifica execucao
46
- #
47
- # @param [String] sql a executar
48
- # @return [Boolean] job ok?
49
- def job_bigquery?(sql)
50
- @job = apibq.query_job(sql)
51
- @job.wait_until_done!
52
- puts @job.error['message'] if @job.failed?
53
- @job.failed?
31
+ # @return [Carteiras] API etherscan - processar transacoes normais e tokens
32
+ def transacoes
33
+ @transacoes ||= Carteiras.new(
34
+ {
35
+ wb: sql("select * from #{BD}.walletEth order by 2")
36
+ .map { |e| { id: e[:id], ax: e[:address], sl: e[:saldo].to_d.round(10) } },
37
+ nt: sql("select blocknumber,iax from #{BD}.ethtx"),
38
+ nk: sql("select blocknumber,iax from #{BD}.ethkx")
39
+ },
40
+ ops
41
+ )
54
42
  end
55
43
 
56
- # cria Data Manipulation Language (DML) job bigquery
57
- #
58
- # @param (see job_bigquery?)
59
- # @return [Integer] numero linhas afetadas
60
- def dml(sql)
61
- job_bigquery?(sql) ? 0 : job.num_dml_affected_rows
44
+ # @return [Carteiras] API etherscan - processar carteiras & transacoes normais e tokens
45
+ def carteiras
46
+ transacoes
62
47
  end
63
48
 
64
- # pesquisa existencia linha folha calculo no bigquery
65
- #
66
- # @return [Google::Cloud::Bigquery::Data] resultado do sql num array<hash>
67
- def sql_select
68
- # array.count = 0 ==> pode carregar esta linha
69
- # array.count >= 1 ==> nao carregar esta linha
70
- @sql = job_bigquery?('select ' + eos_fields + ' ' + sql_where) ? [{}, {}] : job.data
49
+ # insere transacoes novas nas tabelas etht (trx normais), ethk (trx token)
50
+ def processa
51
+ puts(format("%<n>2i LINHAS INSERIDAS #{BD}.etht", n: transacoes.norml.count.positive? ? dml(etht_ins) : 0))
52
+ puts(format("%<n>2i LINHAS INSERIDAS #{BD}.ethk", n: transacoes.token.count.positive? ? dml(ethk_ins) : 0))
71
53
  end
72
54
 
73
- # @return [String] parte sql para processamento linhas existentes
74
- def sql_where
75
- "from hernanirvaz.coins.eos where blocknumber=#{row[0]}"
55
+ # @return [String] comando insert SQL formatado etht (trx normais)
56
+ def etht_ins
57
+ "insert #{BD}.etht(blocknumber,timestamp,txhash,nonce,blockhash,transactionindex,axfrom,axto,value,gas," \
58
+ 'gasprice,iserror,txreceipt_status,input,contractaddress,cumulativegasused,gasused,confirmations,dias' \
59
+ ") VALUES#{transacoes.norml.map { |e| etht_val1(e) }.join(',')}"
76
60
  end
77
61
 
78
- # @return [Integer] numero linhas inseridas
79
- def sql_insert
80
- return 1 unless linha[:i]
62
+ # @return [String] comando insert SQL formatado ethk (trx token)
63
+ def ethk_ins
64
+ "insert #{BD}.ethk(blocknumber,timestamp,txhash,nonce,blockhash,axfrom,contractaddress,axto,value,tokenname," \
65
+ 'tokensymbol,tokendecimal,transactionindex,gas,gasprice,gasused,cumulativegasused,input,confirmations,dias' \
66
+ ") VALUES#{transacoes.token.map { |e| ethk_val1(e) }.join(',')}"
67
+ end
68
+
69
+ # @return [String] valores formatados etht (trx normais parte1)
70
+ def etht_val1(hes)
71
+ "(#{Integer(hes['blockNumber'])}," \
72
+ "#{Integer(hes['timeStamp'])}," \
73
+ "'#{hes['hash']}'," \
74
+ "#{Integer(hes['nonce'])}," \
75
+ "'#{hes['blockHash']}'," \
76
+ "#{Integer(hes['transactionIndex'])}," \
77
+ "'#{hes['from']}'," \
78
+ "'#{hes['to']}'," \
79
+ "#{etht_val2(hes)}"
80
+ end
81
81
 
82
- dml('insert hernanirvaz.coins.eos(' + eos_fields + ') VALUES(' + str_insert1)
82
+ # @return [String] valores formatados etht (trx normais parte2)
83
+ def etht_val2(hes)
84
+ "cast('#{hes['value']}' as numeric)," \
85
+ "cast('#{hes['gas']}' as numeric)," \
86
+ "cast('#{hes['gasPrice']}' as numeric)," \
87
+ "#{Integer(hes['isError'])}," \
88
+ "#{hes['txreceipt_status'].length.zero? ? 'null' : hes['txreceipt_status']}," \
89
+ "#{hes['input'].length.zero? ? 'null' : "'#{hes['input']}'"}," \
90
+ "#{etht_val3(hes)}"
83
91
  end
84
92
 
85
- # @return [String] campos da tabela no bigquery
86
- def eos_fields
87
- 'blocknumber,time,contract,action,acfrom,acto,amount,symbol,memo,data,dias'
93
+ # @return [String] valores formatados etht (trx normais parte3)
94
+ def etht_val3(hes)
95
+ "#{hes['contractAddress'].length.zero? ? 'null' : "'#{hes['contractAddress']}'"}," \
96
+ "0,cast('#{hes['gasUsed']}' as numeric),0," \
97
+ "#{Integer(ops[:h][hes['blockNumber']] || 0)})"
88
98
  end
89
99
 
90
- # @return [String] campos insert da linha bigquery
91
- def str_insert1
92
- "#{row[0]},'#{DateTime.parse(row[1]).strftime(DI)}','#{row[2]}'," + str_insert2
100
+ # @return [String] valores formatados ethk (trx token parte1)
101
+ def ethk_val1(hes)
102
+ "(#{Integer(hes['blockNumber'])}," \
103
+ "#{Integer(hes['timeStamp'])}," \
104
+ "'#{hes['hash']}'," \
105
+ "#{Integer(hes['nonce'])}," \
106
+ "'#{hes['blockHash']}'," \
107
+ "'#{hes['from']}'," \
108
+ "#{ethk_val2(hes)}"
93
109
  end
94
110
 
95
- # @return [String] campos insert da linha bigquery
96
- def str_insert2
97
- "'#{row[3]}','#{row[4]}','#{row[5]}',#{row[6].to_f},'#{row[7]}','#{row[8]}','#{row[9]}',0)"
111
+ # @return [String] valores formatados ethk (trx token parte2)
112
+ def ethk_val2(hes)
113
+ "#{hes['contractAddress'].length.zero? ? 'null' : "'#{hes['contractAddress']}'"}," \
114
+ "'#{hes['to']}'," \
115
+ "cast('#{hes['value']}' as numeric)," \
116
+ "'#{hes['tokenName']}'," \
117
+ "'#{hes['tokenSymbol']}'," \
118
+ "#{Integer(hes['tokenDecimal'])}," \
119
+ "#{Integer(hes['transactionIndex'])}," \
120
+ "#{ethk_val3(hes)}"
98
121
  end
99
122
 
100
- # @return [Integer] numero linhas apagadas
101
- def sql_delete
102
- dml('delete ' + sql_where)
123
+ # @return [String] valores formatados ethk (trx token parte3)
124
+ def ethk_val3(hes)
125
+ "cast('#{hes['gas']}' as numeric)," \
126
+ "cast('#{hes['gasPrice']}' as numeric)," \
127
+ "cast('#{hes['gasUsed']}' as numeric),0," \
128
+ "#{hes['input'].length.zero? ? 'null' : "'#{hes['input']}'"},0," \
129
+ "#{Integer(ops[:h][hes['blockNumber']] || 0)})"
130
+ end
131
+
132
+ # cria job bigquery & verifica execucao
133
+ #
134
+ # @param [String] sql a executar
135
+ # @return [Boolean] job ok?
136
+ def job?(sql)
137
+ @job = api.query_job(sql)
138
+ @job.wait_until_done!
139
+ puts(@job.error['message']) if @job.failed?
140
+ @job.failed?
141
+ end
142
+
143
+ # cria Data Manipulation Language (DML) job bigquery
144
+ #
145
+ # @param (see job?)
146
+ # @return [Integer] numero linhas afetadas
147
+ def dml(sql)
148
+ job?(sql) ? 0 : job.num_dml_affected_rows
149
+ end
150
+
151
+ # cria Structured Query Language (SQL) job bigquery
152
+ #
153
+ # @param (see job?)
154
+ # @return [Array<Hash>] resultados do SQL
155
+ def sql(sql)
156
+ job?(sql) ? [] : job.data
103
157
  end
104
158
  end
105
159
  end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('bigdecimal/util')
4
+
5
+ # @author Hernani Rodrigues Vaz
6
+ module Etht
7
+ # classe para processar carteiras & transacoes normais e tokens
8
+ class Carteiras
9
+ # @return [Etherscan] API etherscan
10
+ attr_reader :api
11
+ # @return [Array<Hash>] todos os dados bigquery
12
+ attr_reader :dbq
13
+ # @return [Thor::CoreExt::HashWithIndifferentAccess] opcoes trabalho
14
+ attr_reader :ops
15
+
16
+ # @param [Hash] dad todos os dados bigquery
17
+ # @param [Thor::CoreExt::HashWithIndifferentAccess] pop opcoes trabalho
18
+ # @option pop [Hash] :h ({}) configuracao dias ajuste reposicionamento temporal
19
+ # @option pop [Boolean] :v (false) mostra dados transacoes normais & tokens?
20
+ # @return [Carteiras] API etherscan - processar transacoes normais e tokens
21
+ def initialize(dad, pop)
22
+ @api = Etherscan.new
23
+ @dbq = dad
24
+ @ops = pop
25
+ end
26
+
27
+ # @return [Array<Hash>] todos os dados etherscan - saldos & transacoes
28
+ def des
29
+ @des ||= api.multi_address_balance(dbq[:wb].map { |c| c[:ax] }).map { |e| base_etherscan(e) }
30
+ end
31
+
32
+ # @return [Array<Hash>] todos os dados juntos bigquery & etherscan
33
+ def djn
34
+ @djn ||= dbq[:wb].map { |b| bigquery_etherscan(b, des.select { |s| b[:ax] == s[:ax] }.first) }
35
+ end
36
+
37
+ # @return [Array<Integer>] lista blocknumbers de transacoes normais
38
+ def bnt
39
+ @bnt ||= (des.map { |e| e[:tx].map { |n| Integer(n['blockNumber']) } }.flatten -
40
+ (ops[:t] ? [] : dbq[:nt].map { |t| t[:blocknumber] })).uniq
41
+ end
42
+
43
+ # @return [Array<Integer>] lista blocknumbers de transacoes token
44
+ def bnk
45
+ @bnk ||= (des.map { |e| e[:kx].map { |n| Integer(n['blockNumber']) } }.flatten -
46
+ (ops[:t] ? [] : dbq[:nk].map { |t| t[:blocknumber] })).uniq
47
+ end
48
+
49
+ # @return [Array<Hash>] lista transacoes normais
50
+ def norml
51
+ @norml ||= des.map { |e| e[:tx].select { |s| bnt.include?(Integer(s['blockNumber'])) } }.flatten.uniq
52
+ end
53
+
54
+ # @return [Array<Hash>] lista transacoes tokan
55
+ def token
56
+ @token ||= des.map { |e| e[:kx].select { |s| bnk.include?(Integer(s['blockNumber'])) } }.flatten.uniq
57
+ end
58
+
59
+ # @return [Array<Hash>] lista ordenada transacoes normais
60
+ def norml_sort
61
+ norml.sort { |a, b| Integer(a['blockNumber']) <=> Integer(b['blockNumber']) }
62
+ end
63
+
64
+ # @return [Array<Hash>] lista ordenada transacoes tokan
65
+ def token_sort
66
+ token.sort { |a, b| Integer(a['blockNumber']) <=> Integer(b['blockNumber']) }
67
+ end
68
+
69
+ # @return [Array<Hash>] lista ordenada todas as transacoes (normais & tokan)
70
+ def todas_sort
71
+ (norml + token).sort { |a, b| Integer(a['blockNumber']) <=> Integer(b['blockNumber']) }
72
+ end
73
+
74
+ # @param [Hash] hes dados etherscan
75
+ # @return [Hash] dados etherscan - address, saldo & transacoes
76
+ def base_etherscan(hes)
77
+ {
78
+ ax: hes['account'],
79
+ sl: (hes['balance'].to_d / 10**18).round(10),
80
+ tx: etherscan_tx(hes['account']),
81
+ kx: etherscan_kx(hes['account'])
82
+ }
83
+ end
84
+
85
+ # @param [Hash] hbq dados bigquery
86
+ # @param [Hash] hes dados etherscan
87
+ # @return [Hash] dados juntos bigquery & etherscan
88
+ def bigquery_etherscan(hbq, hes)
89
+ {
90
+ id: hbq[:id],
91
+ ax: hbq[:ax],
92
+ bs: hbq[:sl],
93
+ bt: dbq[:nt].select { |t| t[:iax] == hbq[:ax] },
94
+ bk: dbq[:nk].select { |t| t[:iax] == hbq[:ax] },
95
+ es: hes[:sl],
96
+ et: hes[:tx],
97
+ ek: hes[:kx]
98
+ }
99
+ end
100
+
101
+ # @param add (see formata_endereco)
102
+ # @return [Array<Hash>] lista transacoes normais ligadas a uma carteira - sem elementos irrelevantes
103
+ def etherscan_tx(add)
104
+ api.normal_tx(add).map do |e|
105
+ e.delete('cumulativeGasUsed')
106
+ e.delete('confirmations')
107
+ e
108
+ end
109
+ end
110
+
111
+ # @param add (see formata_endereco)
112
+ # @return [Array<Hash>] lista transacoes token ligadas a uma carteira - sem elementos irrelevantes
113
+ def etherscan_kx(add)
114
+ api.token_tx(add).map do |e|
115
+ e.delete('cumulativeGasUsed')
116
+ e.delete('confirmations')
117
+ e
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('faraday')
4
+ require('json')
5
+
6
+ # @author Hernani Rodrigues Vaz
7
+ module Etht
8
+ # classe para tratar pedidos etherscan com chave API
9
+ class Etherscan
10
+ attr_reader :key, :url
11
+
12
+ # @param [String] :key apikey a juntar aos pedidos HTTP url:
13
+ # @param [String] :url base URL to use as a prefix for all requests
14
+ # @return [Etherscan] API etherscan base
15
+ def initialize(key: ENV['ETHERSCAN_API_KEY'], url: 'https://api.etherscan.io/')
16
+ @key = key
17
+ @url = url
18
+ end
19
+
20
+ # @return [<Symbol>] adapter for the connection - default :net_http
21
+ def adapter
22
+ @adapter ||= Faraday.default_adapter
23
+ end
24
+
25
+ # manage the default properties and the middleware stack for fulfilling an HTTP request
26
+ #
27
+ # @return [<Faraday::Connection>] connection object with an URL & adapter
28
+ def conn
29
+ @conn ||=
30
+ Faraday.new(url: url) do |c|
31
+ c.request(:url_encoded)
32
+ c.adapter(adapter)
33
+ end
34
+ end
35
+
36
+ # @return [<Hash>] resultado json do GET HTTP request
37
+ def get(**ars)
38
+ r =
39
+ conn.get('api') do |o|
40
+ o.headers = { content_type: 'application/json', accept: 'application/json', user_agent: 'etherscan;ruby' }
41
+ o.params = ars.merge({ apikey: key })
42
+ end
43
+ raise(Etht::Erro, r) if r.status != 200
44
+
45
+ JSON(r.body)['result']
46
+ end
47
+
48
+ # get ether balance for a single address
49
+ #
50
+ # @param [String] add address for balance
51
+ # @return [<String>] devolve saldo
52
+ def address_balance(add)
53
+ raise(Etht::Erro, 'address must be defined') if add.nil?
54
+
55
+ get(module: 'account', action: 'balance', address: add, tag: 'latest')
56
+ end
57
+
58
+ # get ether balance for multiple addresses
59
+ #
60
+ # @param [String] ads lista de addresses (max 20)
61
+ # @return [Array<Hash>] devolve lista com contas & saldo
62
+ def multi_address_balance(ads)
63
+ raise(Etht::Erro, 'up to 20 accounts in a single batch') if ads.size > 20
64
+
65
+ get(module: 'account', action: 'balancemulti', address: ads.join(','), tag: 'latest')
66
+ end
67
+
68
+ # get ERC20-token account balance
69
+ #
70
+ # @param add (see address_balance)
71
+ # @param [String] cdd token contract address
72
+ # @return (see address_balance)
73
+ def token_balance(add, cdd)
74
+ raise(Etht::Erro, 'contract or address must be defined') if (cdd || add).nil?
75
+
76
+ get(module: 'account', action: 'tokenbalance', address: add, contractaddress: cdd)
77
+ end
78
+
79
+ # get a list of normal transactions by address
80
+ #
81
+ # @param [String] add address for transactions
82
+ # @param [Hash] ars opcoes trabalho
83
+ # @option ars [String] :start_block starting blockNo to retrieve results
84
+ # @option ars [String] :end_block ending blockNo to retrieve results
85
+ # @option ars [String] :sort asc -> ascending order, desc -> descending order
86
+ # @option ars [String] :page to get paginated results
87
+ # @option ars [String] :offset max records to return
88
+ # @return [Array<Hash>] devolve ate max 10000 das ultimas transacoes
89
+ def normal_tx(add, **ars)
90
+ raise(Etht::Erro, 'address must be defined') if add.nil?
91
+
92
+ transcations('txlist', add, nil, **ars)
93
+ end
94
+
95
+ # get a list of ERC20 - token transfer events
96
+ #
97
+ # @param add (see normal_tx)
98
+ # @param [String] cdd token address (nil to get a list of all ERC20 transactions)
99
+ # @param ars (see normal_tx)
100
+ # @option ars (see normal_tx)
101
+ # @return (see normal_tx)
102
+ def token_tx(add, cdd = nil, **ars)
103
+ raise(Etht::Erro, 'contract or address must be defined') if (cdd || add).nil?
104
+
105
+ transcations('tokentx', add, cdd, **ars)
106
+ end
107
+
108
+ private
109
+
110
+ # get a list of transactions
111
+ #
112
+ # @param [String] act accao a executar
113
+ # @param add (see normal_tx)
114
+ # @param cdd (see token_tx)
115
+ # @param ars (see normal_tx)
116
+ # @option ars (see normal_tx)
117
+ # @return (see normal_tx)
118
+ def transcations(act, add, cdd, **ars)
119
+ get(**{ module: 'account', action: act, address: add }.merge({
120
+ contractaddress: cdd,
121
+ startblock: ars[:start_block],
122
+ endblock: ars[:end_block],
123
+ page: ars[:page],
124
+ offset: ars[:offset],
125
+ sort: ars[:sort]
126
+ }.reject { |_, v| v.nil? }))
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @author Hernani Rodrigues Vaz
4
+ module Etht
5
+ # (see Carteiras)
6
+ class Carteiras
7
+ # @param [String] add endereco carteira ether
8
+ # @param [Integer] max chars a mostrar
9
+ # @return [String] endereco ether formatado
10
+ # @example ether address inicio..fim
11
+ # 0x10f3a0cf0b534c..c033cf32e8a03586
12
+ def formata_endereco(add, max)
13
+ i = Integer((max - 2) / 2)
14
+ e = (max <= 20 ? dbq[:wb].select { |s| s[:ax] == add }.first : nil) || { id: add }
15
+ max < 7 ? 'erro' : "#{e[:id][0, i - 3]}..#{add[-i - 3..]}"
16
+ end
17
+
18
+ # @parm [Hash] hjn dados juntos bigquery & etherscan
19
+ # @return [String] texto formatado duma carteira
20
+ def formata_carteira(hjn)
21
+ format(
22
+ '%<s1>-6.6s %<s2>-34.34s ',
23
+ s1: hjn[:id],
24
+ s2: formata_endereco(hjn[:ax], 34)
25
+ ) + formata_valores(hjn)
26
+ end
27
+
28
+ # @parm (see formata_carteira)
29
+ # @return [Boolean] carteira tem transacoes novas(sim=NOK, nao=OK)?
30
+ def ok?(hjn)
31
+ hjn[:bs] == hjn[:es] && hjn[:bt].count == hjn[:et].count && hjn[:bk].count == hjn[:ek].count
32
+ end
33
+
34
+ # @parm (see formata_carteira)
35
+ # @return [String] texto formatado valores duma carteira
36
+ def formata_valores(hjn)
37
+ format(
38
+ '%<v1>10.6f %<n1>2i %<n3>2i %<v2>11.6f %<n2>2i %<n4>2i %<ok>-3s',
39
+ v1: hjn[:bs],
40
+ n1: hjn[:bt].count,
41
+ n3: hjn[:bk].count,
42
+ v2: hjn[:es],
43
+ n2: hjn[:et].count,
44
+ n4: hjn[:ek].count,
45
+ ok: ok?(hjn) ? 'OK' : 'NOK'
46
+ )
47
+ end
48
+
49
+ # @parm [Hash] htx transacao normal
50
+ # @return [String] texto formatado transacao normal
51
+ def formata_transacao_norml(htx)
52
+ format(
53
+ '%<bn>9i %<fr>-20.20s %<to>-20.20s %<dt>10.10s %<vl>17.6f',
54
+ bn: htx['blockNumber'],
55
+ fr: formata_endereco(htx['from'], 20),
56
+ to: formata_endereco(htx['to'], 20),
57
+ dt: Time.at(Integer(htx['timeStamp'])),
58
+ vl: (htx['value'].to_d / 10**18).round(10)
59
+ )
60
+ end
61
+
62
+ # @parm [Hash] hkx transacao token
63
+ # @return [String] texto formatado transacao token
64
+ def formata_transacao_token(hkx)
65
+ format(
66
+ '%<bn>9i %<fr>-20.20s %<to>-20.20s %<dt>10.10s %<sy>-5.5s %<vl>11.3f',
67
+ bn: hkx['blockNumber'],
68
+ fr: formata_endereco(hkx['from'], 20),
69
+ to: formata_endereco(hkx['to'], 20),
70
+ dt: Time.at(Integer(hkx['timeStamp'])),
71
+ vl: (hkx['value'].to_d / 10**18).round(10),
72
+ sy: hkx['tokenSymbol']
73
+ )
74
+ end
75
+
76
+ # @return [String] texto carteiras & transacoes & ajuste dias
77
+ def mostra_resumo
78
+ return unless djn.count.positive?
79
+
80
+ puts("\nid address ----bigquery---- ----etherscan----")
81
+ djn.each { |e| puts(formata_carteira(e)) }
82
+ mostra_transacao_norml
83
+ mostra_transacao_token
84
+ mostra_configuracao_ajuste_dias
85
+ end
86
+
87
+ # @return [String] texto transacoes normais
88
+ def mostra_transacao_norml
89
+ return unless ops[:v] && norml.count.positive?
90
+
91
+ puts("\ntx normal address from address to ---data--- ------valor------")
92
+ norml_sort.each { |e| puts(formata_transacao_norml(e)) }
93
+ end
94
+
95
+ # @return [String] texto transacoes token
96
+ def mostra_transacao_token
97
+ return unless ops[:v] && token.count.positive?
98
+
99
+ puts("\ntx token address from address to ---data--- token ---valor---")
100
+ token_sort.each { |e| puts(formata_transacao_token(e)) }
101
+ end
102
+
103
+ # @return [String] texto configuracao ajuste dias das transacoes (normais & tokan)
104
+ def mostra_configuracao_ajuste_dias
105
+ return unless (norml.count + token.count).positive?
106
+
107
+ puts("\nstring ajuste dias\n-h=#{todas_sort.map { |e| "#{e['blockNumber']}:0" }.join(' ')}")
108
+ end
109
+ end
110
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Etht
4
- VERSION = '0.1.3'
4
+ VERSION = '0.1.8'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: etht
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hernâni Rodrigues Vaz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-08-09 00:00:00.000000000 Z
11
+ date: 2020-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: etherscan_api
42
+ name: faraday
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: json
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: thor
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -94,8 +108,8 @@ dependencies:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
97
- description: Arquiva eth-transactions no bigquery. Pode apagar movimentos existentes
98
- ja no bigquery.
111
+ description: Arquiva eth-transactions no bigquery. Pode ajustar dias para reposicionamento
112
+ temporal.
99
113
  email:
100
114
  - hernanirvaz@gmail.com
101
115
  executables:
@@ -104,6 +118,7 @@ extensions: []
104
118
  extra_rdoc_files: []
105
119
  files:
106
120
  - ".gitignore"
121
+ - ".rubocop.yml"
107
122
  - CODE_OF_CONDUCT.md
108
123
  - Gemfile
109
124
  - Gemfile.lock
@@ -116,6 +131,9 @@ files:
116
131
  - exe/etht
117
132
  - lib/etht.rb
118
133
  - lib/etht/bigquery.rb
134
+ - lib/etht/carteiras.rb
135
+ - lib/etht/etherscan.rb
136
+ - lib/etht/formatar.rb
119
137
  - lib/etht/version.rb
120
138
  homepage: https://github.com/hernanirvaz/etht
121
139
  licenses: