etht 0.1.4 → 0.1.9

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: 9ca8ae2c29d35644a3d72e1ed819048020838736539e71023d5b81a0808875a5
4
- data.tar.gz: c40d028004ed756b4805eed8e89a71e0a7efc35de52bee891ebe7c00bc358abd
3
+ metadata.gz: dcc50ecfc4f0b90106f5ed792e2c870d6c6d00283f2f9b18005b27c28eeef15e
4
+ data.tar.gz: b20bbd45722191045ce9b3ade80590463e8f6529b1af9af4ed045b9a4ea4986a
5
5
  SHA512:
6
- metadata.gz: 005a56ce7e56e95fa9bcff843e9077a129f3492e5aa149a78d7f184307a88bc68e058004159fddeabd04c5b41eacec5eae405445a8d091b41dcdf9e5dd81f761
7
- data.tar.gz: 5707e919d084a23f8115b3516db8a3979555decb1b682245fba6249f31a46166ee8047687eeb3390eec0a9c59f8f8c4aa7e75d4ed09acff2d2f4a9b936b1bae0
6
+ metadata.gz: 917ffdc49fe414e5a28fcd92c1c5d8308bfb7833c8a4acfcbe648e4726be9be914319f7c99d6326177a0bab1880b7399e1cb8c0cf655f220cc12bf30576b877f
7
+ data.tar.gz: 6b69eb9c67c40d66d224094e99b478b71128f7b5c53c4ab2c3496136dc58b2a348d58a61d25aef8b93d24bad4ecd3e2952b51123df618342857ce90c46ff48aa
@@ -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,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- etht (0.1.4)
4
+ etht (0.1.9)
5
5
  faraday
6
6
  google-cloud-bigquery
7
7
  json
@@ -47,7 +47,7 @@ GEM
47
47
  signet (~> 0.14)
48
48
  httpclient (2.8.3)
49
49
  json (2.3.1)
50
- jwt (2.2.1)
50
+ jwt (2.2.2)
51
51
  memoist (0.16.2)
52
52
  mini_mime (1.0.2)
53
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,19 +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 'faraday'
34
- spec.add_dependency 'google-cloud-bigquery'
35
- spec.add_dependency 'json'
36
- spec.add_dependency 'thor'
37
- 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')
38
39
  end
@@ -1,36 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thor'
4
- require 'etht/bigquery'
5
- require 'etht/etherscan'
6
- 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')
7
9
 
8
10
  # @author Hernani Rodrigues Vaz
9
11
  module Etht
10
- ID = `whoami`.chomp
11
-
12
- class Error < StandardError; end
13
- # class Exception < StandardError
14
- class Exception < StandardError
15
- def initialize(message)
16
- super(message)
12
+ # classe para erros desta gem
13
+ class Erro < StandardError
14
+ # @return [StandardError] personalizacao dos erros
15
+ def initialize(msg)
16
+ super(msg)
17
17
  end
18
18
  end
19
19
 
20
- # CLI para carregar etherscan comuns no bigquery
20
+ # classe para carregar/mostrar dados comuns bigquery & etherscan
21
21
  class CLI < Thor
22
- desc 'work', 'carrega/apaga dados do etherscan'
23
- option :e, type: :boolean, default: false, desc: 'apaga linha igual'
24
- option :m, type: :boolean, default: false, desc: 'apaga linhas existencia multipla'
25
- # 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
26
25
  def work
27
- Bigquery.new({ e: options[:e], m: options[:m], i: true }).processa_eth
26
+ Bigquery.new(options).processa
28
27
  end
29
28
 
30
- desc 'show', 'mostra dados do etherscan'
31
- # 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
32
33
  def show
33
- Bigquery.new.processa_eth
34
+ Bigquery.new(options).carteiras.mostra_resumo
34
35
  end
35
36
 
36
37
  default_task :show
@@ -1,103 +1,162 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'google/cloud/bigquery'
3
+ require('google/cloud/bigquery')
4
+ require('bigdecimal/util')
4
5
 
5
6
  # @author Hernani Rodrigues Vaz
6
7
  module Etht
7
- DF = '%Y-%m-%d'
8
- DI = '%Y-%m-%d %H:%M:%S'
8
+ BD = 'hernanirvaz.coins'
9
9
 
10
- # (see Bigquery)
10
+ # classe para processar etherscan & bigquery
11
11
  class Bigquery
12
12
  # @return [Google::Cloud::Bigquery] API bigquery
13
- attr_reader :apibq
14
- # @return [Etherscan::Api] API etherscan
15
- attr_reader :apies
16
-
17
- # @return [Array] row folha calculo em processamento
18
- attr_reader :row
13
+ attr_reader :api
19
14
  # @return [Google::Cloud::Bigquery::QueryJob] job bigquery
20
15
  attr_reader :job
21
- # @return (see sql_select)
22
- attr_reader :sql
23
-
24
- # @param [String] csv folha calculo para processar
25
- # @param [Hash<Symbol, Boolean>] ops opcoes trabalho com linhas
26
- # @option ops [Boolean] :e (false) apaga linha igual?
27
- # @option ops [Boolean] :m (false) apaga linhas existencia multipla?
28
- # @option ops [Boolean] :i (false) insere linha nova?
29
- # @return [Bigquery] acesso folhas calculo bloks.io & correspondente bigquery dataset
30
- def initialize(ops = { e: false, m: false, i: false })
16
+ # @return [Thor::CoreExt::HashWithIndifferentAccess] opcoes trabalho
17
+ attr_reader :ops
18
+ # @return (see sql)
19
+ attr_reader :sqr
20
+
21
+ # @param [Thor::CoreExt::HashWithIndifferentAccess] pop opcoes trabalho
22
+ # @option pop [Hash] :h ({}) configuracao ajuste reposicionamento temporal
23
+ # @option pop [Boolean] :v (false) mostra transacoes normais & token?
24
+ # @option pop [Boolean] :t (false) mostra transacoes todas ou somente novas?
25
+ # @return [Bigquery] API bigquery & API etherscan
26
+ def initialize(pop)
31
27
  # usa env GOOGLE_APPLICATION_CREDENTIALS para obter credentials
32
28
  # @see https://cloud.google.com/bigquery/docs/authentication/getting-started
33
- @apibq = Google::Cloud::Bigquery.new
34
- @apies = Etht::Accounts.new
35
- @linha = ops
29
+ @api = Google::Cloud::Bigquery.new
30
+ @ops = pop
36
31
  end
37
32
 
38
- def processa_eth
39
- contract_address = '0xfc325129a11fab241287e42a9f04a74f14077b77'
40
- p apies.normal_transactions(contract_address)
33
+ # @return [Carteiras] API etherscan - processar transacoes normais e tokens
34
+ def transacoes
35
+ @transacoes ||= Carteiras.new(
36
+ {
37
+ wb: sql("select * from #{BD}.walletEth order by 2")
38
+ .map { |e| { id: e[:id], ax: e[:address], sl: e[:saldo].to_d.round(10) } },
39
+ nt: sql("select blocknumber,iax from #{BD}.ethtx"),
40
+ nk: sql("select blocknumber,iax from #{BD}.ethkx")
41
+ },
42
+ ops
43
+ )
41
44
  end
42
45
 
43
- # cria job bigquery & verifica execucao
44
- #
45
- # @param [String] sql a executar
46
- # @return [Boolean] job ok?
47
- def job_bigquery?(sql)
48
- @job = apibq.query_job(sql)
49
- @job.wait_until_done!
50
- puts @job.error['message'] if @job.failed?
51
- @job.failed?
46
+ # @return [Carteiras] API etherscan - processar carteiras & transacoes normais e tokens
47
+ def carteiras
48
+ transacoes
52
49
  end
53
50
 
54
- # cria Data Manipulation Language (DML) job bigquery
55
- #
56
- # @param (see job_bigquery?)
57
- # @return [Integer] numero linhas afetadas
58
- def dml(sql)
59
- job_bigquery?(sql) ? 0 : job.num_dml_affected_rows
51
+ # insere transacoes novas nas tabelas etht (trx normais), ethk (trx token)
52
+ def processa
53
+ puts(format("%<n>2i LINHAS INSERIDAS #{BD}.etht", n: transacoes.norml.count.positive? ? dml(etht_ins) : 0))
54
+ puts(format("%<n>2i LINHAS INSERIDAS #{BD}.ethk", n: transacoes.token.count.positive? ? dml(ethk_ins) : 0))
60
55
  end
61
56
 
62
- # pesquisa existencia linha folha calculo no bigquery
63
- #
64
- # @return [Google::Cloud::Bigquery::Data] resultado do sql num array<hash>
65
- def sql_select
66
- # array.count = 0 ==> pode carregar esta linha
67
- # array.count >= 1 ==> nao carregar esta linha
68
- @sql = job_bigquery?('select ' + eos_fields + ' ' + sql_where) ? [{}, {}] : job.data
57
+ # @return [String] comando insert SQL formatado etht (trx normais)
58
+ def etht_ins
59
+ "insert #{BD}.etht(blocknumber,timestamp,txhash,nonce,blockhash,transactionindex,axfrom,axto,value,gas," \
60
+ 'gasprice,iserror,txreceipt_status,input,contractaddress,cumulativegasused,gasused,confirmations,dias' \
61
+ ") VALUES#{transacoes.norml.map { |e| etht_val1(e) }.join(',')}"
62
+ end
63
+
64
+ # @return [String] comando insert SQL formatado ethk (trx token)
65
+ def ethk_ins
66
+ "insert #{BD}.ethk(blocknumber,timestamp,txhash,nonce,blockhash,axfrom,contractaddress,axto,value,tokenname," \
67
+ 'tokensymbol,tokendecimal,transactionindex,gas,gasprice,gasused,cumulativegasused,input,confirmations,dias' \
68
+ ") VALUES#{transacoes.token.map { |e| ethk_val1(e) }.join(',')}"
69
+ end
70
+
71
+ # @return [String] valores formatados etht (trx normais parte1)
72
+ def etht_val1(hes)
73
+ "(#{Integer(hes['blockNumber'])}," \
74
+ "#{Integer(hes['timeStamp'])}," \
75
+ "'#{hes['hash']}'," \
76
+ "#{Integer(hes['nonce'])}," \
77
+ "'#{hes['blockHash']}'," \
78
+ "#{Integer(hes['transactionIndex'])}," \
79
+ "'#{hes['from']}'," \
80
+ "'#{hes['to']}'," \
81
+ "#{etht_val2(hes)}"
69
82
  end
70
83
 
71
- # @return [String] parte sql para processamento linhas existentes
72
- def sql_where
73
- "from hernanirvaz.coins.eos where blocknumber=#{row[0]}"
84
+ # @return [String] valores formatados etht (trx normais parte2)
85
+ def etht_val2(hes)
86
+ "cast('#{hes['value']}' as numeric)," \
87
+ "cast('#{hes['gas']}' as numeric)," \
88
+ "cast('#{hes['gasPrice']}' as numeric)," \
89
+ "#{Integer(hes['isError'])}," \
90
+ "#{hes['txreceipt_status'].length.zero? ? 'null' : hes['txreceipt_status']}," \
91
+ "#{hes['input'].length.zero? ? 'null' : "'#{hes['input']}'"}," \
92
+ "#{etht_val3(hes)}"
74
93
  end
75
94
 
76
- # @return [Integer] numero linhas inseridas
77
- def sql_insert
78
- return 1 unless linha[:i]
95
+ # @return [String] valores formatados etht (trx normais parte3)
96
+ def etht_val3(hes)
97
+ "#{hes['contractAddress'].length.zero? ? 'null' : "'#{hes['contractAddress']}'"}," \
98
+ "0,cast('#{hes['gasUsed']}' as numeric),0," \
99
+ "#{Integer(ops[:h][hes['blockNumber']] || 0)})"
100
+ end
79
101
 
80
- dml('insert hernanirvaz.coins.eos(' + eos_fields + ') VALUES(' + str_insert1)
102
+ # @return [String] valores formatados ethk (trx token parte1)
103
+ def ethk_val1(hes)
104
+ "(#{Integer(hes['blockNumber'])}," \
105
+ "#{Integer(hes['timeStamp'])}," \
106
+ "'#{hes['hash']}'," \
107
+ "#{Integer(hes['nonce'])}," \
108
+ "'#{hes['blockHash']}'," \
109
+ "'#{hes['from']}'," \
110
+ "#{ethk_val2(hes)}"
81
111
  end
82
112
 
83
- # @return [String] campos da tabela no bigquery
84
- def eos_fields
85
- 'blocknumber,time,contract,action,acfrom,acto,amount,symbol,memo,data,dias'
113
+ # @return [String] valores formatados ethk (trx token parte2)
114
+ def ethk_val2(hes)
115
+ "#{hes['contractAddress'].length.zero? ? 'null' : "'#{hes['contractAddress']}'"}," \
116
+ "'#{hes['to']}'," \
117
+ "cast('#{hes['value']}' as numeric)," \
118
+ "'#{hes['tokenName']}'," \
119
+ "'#{hes['tokenSymbol']}'," \
120
+ "#{Integer(hes['tokenDecimal'])}," \
121
+ "#{Integer(hes['transactionIndex'])}," \
122
+ "#{ethk_val3(hes)}"
86
123
  end
87
124
 
88
- # @return [String] campos insert da linha bigquery
89
- def str_insert1
90
- "#{row[0]},'#{DateTime.parse(row[1]).strftime(DI)}','#{row[2]}'," + str_insert2
125
+ # @return [String] valores formatados ethk (trx token parte3)
126
+ def ethk_val3(hes)
127
+ "cast('#{hes['gas']}' as numeric)," \
128
+ "cast('#{hes['gasPrice']}' as numeric)," \
129
+ "cast('#{hes['gasUsed']}' as numeric),0," \
130
+ "#{hes['input'].length.zero? ? 'null' : "'#{hes['input']}'"},0," \
131
+ "#{Integer(ops[:h][hes['blockNumber']] || 0)})"
91
132
  end
92
133
 
93
- # @return [String] campos insert da linha bigquery
94
- def str_insert2
95
- "'#{row[3]}','#{row[4]}','#{row[5]}',#{row[6].to_f},'#{row[7]}','#{row[8]}','#{row[9]}',0)"
134
+ # cria job bigquery & verifica execucao
135
+ #
136
+ # @param cmd (see sql)
137
+ # @return [Boolean] job ok?
138
+ def job?(cmd)
139
+ @job = api.query_job(cmd)
140
+ @job.wait_until_done!
141
+ puts(@job.error['message']) if @job.failed?
142
+ @job.failed?
96
143
  end
97
144
 
98
- # @return [Integer] numero linhas apagadas
99
- def sql_delete
100
- dml('delete ' + sql_where)
145
+ # cria Structured Query Language (SQL) job bigquery
146
+ #
147
+ # @param [String] cmd comando SQL a executar
148
+ # @param [String] red resultado quando SQL tem erro
149
+ # @return [Google::Cloud::Bigquery::Data] resultado do SQL
150
+ def sql(cmd, red = [])
151
+ @sqr = job?(cmd) ? red : job.data
152
+ end
153
+
154
+ # cria Data Manipulation Language (DML) job bigquery
155
+ #
156
+ # @param cmd (see sql)
157
+ # @return [Integer] numero linhas afetadas
158
+ def dml(cmd)
159
+ job?(cmd) ? 0 : job.num_dml_affected_rows
101
160
  end
102
161
  end
103
162
  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
@@ -1,177 +1,129 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'faraday'
4
- require 'json'
3
+ require('faraday')
4
+ require('json')
5
5
 
6
6
  # @author Hernani Rodrigues Vaz
7
7
  module Etht
8
- # class Config
9
- class Config
10
- attr_accessor :key, :secret, :url, :raise_exceptions
11
- attr_writer :logger
8
+ # classe para tratar pedidos etherscan com chave API
9
+ class Etherscan
10
+ attr_reader :key, :url
12
11
 
13
- def logger
14
- @logger ||= Logger.new(STDERR)
15
- end
16
- end
17
-
18
- # class Api
19
- class Api
20
- attr_reader :connection
21
-
22
- def initialize(params = {})
23
- @connection = Etht::Client.new(params)
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
24
18
  end
25
19
 
26
- def get(params)
27
- response = connection.get(params)
28
- response['result']
20
+ # @return [<Symbol>] adapter for the connection - default :net_http
21
+ def adapter
22
+ @adapter ||= Faraday.default_adapter
29
23
  end
30
- end
31
24
 
32
- # class Client
33
- class Client
34
- URL = 'https://api.etherscan.io/'
35
- HEADERS = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }.freeze
36
-
37
- attr_reader :key, :url, :user_agent, :headers, :raise_exceptions
38
- attr_writer :adapter, :conn
39
-
40
- def initialize(params = {})
41
- @key = params.fetch(:key, Etht.config.key)
42
- @url = params.fetch(:url, Etht.config.url || URL)
43
- @adapter = params.fetch(:adapter, adapter)
44
- @conn = params.fetch(:conn, conn)
45
- @user_agent = params.fetch(:user_agent, "etherscan/#{Etht::VERSION};ruby")
46
- @headers = HEADERS.merge('User-Agent' => @user_agent)
47
- @raise_exceptions = params.fetch(:raise_exceptions, Etht.config.raise_exceptions || true)
48
- yield self if block_given?
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
49
34
  end
50
35
 
51
- def get(params = {})
52
- endpoint = 'api'
53
- merged_params = params.merge({ apikey: key })
54
- response = conn.get(endpoint) do |req|
55
- req.headers = headers
56
- req.params = merged_params
57
- end
58
- raise Etht::Exception, response if raise_exceptions? && response.status != 200
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
59
44
 
60
- JSON(response.body)
45
+ JSON(r.body)['result']
61
46
  end
62
47
 
63
- def conn
64
- @conn ||= Faraday.new(url: @url) do |conn|
65
- conn.request :url_encoded
66
- conn.adapter adapter
67
- end
68
- end
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?
69
54
 
70
- def adapter
71
- @adapter ||= Faraday.default_adapter
55
+ get(module: 'account', action: 'balance', address: add, tag: 'latest')
72
56
  end
73
57
 
74
- def raise_exceptions?
75
- @raise_exceptions
76
- end
77
- end
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
78
64
 
79
- # class Accounts < Etht::Api
80
- class Accounts < Api
81
- def address_balance(address)
82
- params = {
83
- module: 'account', action: 'balance',
84
- address: address, tag: 'latest'
85
- }
86
- get(params)
65
+ get(module: 'account', action: 'balancemulti', address: ads.join(','), tag: 'latest')
87
66
  end
88
67
 
89
- def multi_address_balance(addresses)
90
- raise Etht::Exception, 'up to 20 accounts in a single batch' if addresses.size > 20
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?
91
75
 
92
- params = {
93
- module: 'account', action: 'balancemulti',
94
- address: addresses.join(','), tag: 'latest'
95
- }
96
- get(params)
76
+ get(module: 'account', action: 'tokenbalance', address: add, contractaddress: cdd)
97
77
  end
98
78
 
99
- # Get a list of 'Normal' Transactions By Address
100
- # if page is not defined Returns up to 10000 last transactions
101
- # Available args: start_block, end_block, sort, page, offset
102
- # @param sort 'asc' -> ascending order, 'des' -> descending order
103
- # @param start_block starting blockNo to retrieve results
104
- # @param end_block ending blockNo to retrieve results
105
- # @param page Paginated result <page number>
106
- # @param offset max records to return
107
- def normal_transactions(address, args = {})
108
- action = 'txlist'
109
- transcations(action, address, nil, args)
110
- end
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?
111
91
 
112
- # Get a list of 'Internal' Transactions By Address
113
- # Available args: start_block, end_block, sort, page, offset
114
- def internal_transactions(address, args = {})
115
- action = 'txlistinternal'
116
- transcations(action, address, nil, args)
92
+ transcations('txlist', add, nil, **ars)
117
93
  end
118
94
 
119
- # Get a list of "ERC20 - Token Transfer Events"
120
- # @param contract_address Token address (set nil to get a list of all ERC20 transactions)
121
- # @param address Address for ERC20 transactions (optional)
122
- # Available args: start_block, end_block, sort, page, offset
123
- def token_transactions(contract_address, address = nil, args = {})
124
- raise Etht::Exception, 'contract or address must be defined' if (contract_address || address).nil?
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?
125
104
 
126
- action = 'tokentx'
127
- transcations(action, address, contract_address, args)
105
+ transcations('tokentx', add, cdd, **ars)
128
106
  end
129
107
 
130
108
  private
131
109
 
132
- def transcations(action, address, contract_address, args)
133
- params = {
134
- module: 'account', action: action,
135
- address: address, contractaddress: contract_address,
136
- startblock: args[:start_block], endblock: args[:end_block],
137
- page: args[:page], offset: args[:offset], sort: args[:sort]
138
- }.reject { |_, v| v.nil? }
139
- get(params)
140
- end
141
- end
142
-
143
- # class Tokens < Etht::Api
144
- class Tokens < Api
145
- def total_supply(contract_address)
146
- params = {
147
- module: 'stats', action: 'tokensupply',
148
- contractaddress: contract_address
149
- }
150
- get(params)
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? }))
151
127
  end
152
-
153
- def balance(address, contract_address)
154
- params = {
155
- module: 'account', action: 'tokenbalance',
156
- address: address, contractaddress: contract_address
157
- }
158
- get(params)
159
- end
160
- end
161
-
162
- def self.configure
163
- yield config
164
- end
165
-
166
- def self.config
167
- @config ||= Config.new
168
- end
169
-
170
- def self.logger
171
- config.logger
172
- end
173
-
174
- configure do |config|
175
- config.key = ENV['ETHERSCAN_API_KEY']
176
128
  end
177
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.4'
4
+ VERSION = '0.1.9'
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.4
4
+ version: 0.1.9
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-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,8 +108,8 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
- description: Arquiva eth-transactions no bigquery. Pode apagar movimentos existentes
112
- ja no bigquery.
111
+ description: Arquiva eth-transactions no bigquery. Pode ajustar dias para reposicionamento
112
+ temporal.
113
113
  email:
114
114
  - hernanirvaz@gmail.com
115
115
  executables:
@@ -118,6 +118,7 @@ extensions: []
118
118
  extra_rdoc_files: []
119
119
  files:
120
120
  - ".gitignore"
121
+ - ".rubocop.yml"
121
122
  - CODE_OF_CONDUCT.md
122
123
  - Gemfile
123
124
  - Gemfile.lock
@@ -130,7 +131,9 @@ files:
130
131
  - exe/etht
131
132
  - lib/etht.rb
132
133
  - lib/etht/bigquery.rb
134
+ - lib/etht/carteiras.rb
133
135
  - lib/etht/etherscan.rb
136
+ - lib/etht/formatar.rb
134
137
  - lib/etht/version.rb
135
138
  homepage: https://github.com/hernanirvaz/etht
136
139
  licenses: