etht 0.1.3 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: