etht 0.1.4 → 0.1.5

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: f5a76fade4364cb9ecc118fcfae181f13d84fe3e39ba85a6078e8974ab403652
4
+ data.tar.gz: 269e68983b59463ec5aaeb3e0179b28f79f6a43135e2401269c467e0d90a74c4
5
5
  SHA512:
6
- metadata.gz: 005a56ce7e56e95fa9bcff843e9077a129f3492e5aa149a78d7f184307a88bc68e058004159fddeabd04c5b41eacec5eae405445a8d091b41dcdf9e5dd81f761
7
- data.tar.gz: 5707e919d084a23f8115b3516db8a3979555decb1b682245fba6249f31a46166ee8047687eeb3390eec0a9c59f8f8c4aa7e75d4ed09acff2d2f4a9b936b1bae0
6
+ metadata.gz: 9f446bfb04b77cf6d7cfc366f12b564a6385837484d388121341dbb4fa26d36e07f30d3883871d8e7bbbe1a353a3ed838fa95a2aa5b1d15026a8fc099f6e8389
7
+ data.tar.gz: 65936552f2176a0706e417579fa6747a8c7fcc9937a23d1f83fca413c2a964c6e37ee58cfd1dd086e964367d723e0ce828a130e182043129c4cc84117227ae75
@@ -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.5)
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,36 @@
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 carteiras e dados bigquery & etherscan'
30
+ option :v, type: :boolean, default: false, desc: 'mostra dados transacoes normais & token'
31
+ # mostra carteiras e dados bigquery & etherscan
32
32
  def show
33
- Bigquery.new.processa_eth
33
+ Bigquery.new(options).carteiras.mostra
34
34
  end
35
35
 
36
36
  default_task :show
@@ -1,103 +1,153 @@
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
+
19
+ # @param [Thor::CoreExt::HashWithIndifferentAccess] pop opcoes trabalho
20
+ # @option pop [Hash] :h ({}) configuracao ajuste reposicionamento temporal
21
+ # @option pop [Boolean] :v (false) mostra dados transacoes normais & token?
22
+ # @return [Bigquery] API bigquery & API etherscan
23
+ def initialize(pop)
31
24
  # usa env GOOGLE_APPLICATION_CREDENTIALS para obter credentials
32
25
  # @see https://cloud.google.com/bigquery/docs/authentication/getting-started
33
- @apibq = Google::Cloud::Bigquery.new
34
- @apies = Etht::Accounts.new
35
- @linha = ops
26
+ @api = Google::Cloud::Bigquery.new
27
+ @ops = pop
36
28
  end
37
29
 
38
- def processa_eth
39
- contract_address = '0xfc325129a11fab241287e42a9f04a74f14077b77'
40
- p apies.normal_transactions(contract_address)
30
+ # @return [Carteiras] API etherscan - processar transacoes normais e tokens
31
+ def carteiras
32
+ @carteiras ||= Carteiras.new(
33
+ {
34
+ wb: sql("select * from #{BD}.walletEth order by 2")
35
+ .map { |e| { id: e[:id], ax: e[:address], sl: e[:saldo].to_d.round(10) } },
36
+ nt: sql("select blocknumber,iax from #{BD}.ethtx"),
37
+ nk: sql("select blocknumber,iax from #{BD}.ethkx")
38
+ },
39
+ ops
40
+ )
41
41
  end
42
42
 
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?
43
+ # insere transacoes novas nas tabelas etht, ethk
44
+ def processa
45
+ puts(format("%<n>2i LINHAS INSERIDAS #{BD}.etht", n: carteiras.novaest.count.positive? ? dml(etht_ins) : 0))
46
+ puts(format("%<n>2i LINHAS INSERIDAS #{BD}.ethk", n: carteiras.novaesk.count.positive? ? dml(ethk_ins) : 0))
52
47
  end
53
48
 
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
49
+ # @return [String] comando insert SQL formatado bigquery.etht
50
+ def etht_ins
51
+ "insert #{BD}.etht(blocknumber,timestamp,txhash,nonce,blockhash,transactionindex,axfrom,axto,value,gas," \
52
+ 'gasprice,iserror,txreceipt_status,input,contractaddress,cumulativegasused,gasused,confirmations,dias' \
53
+ ") VALUES(#{carteiras.novaest.map { |e| etht_val1(e) }.join(',')})"
60
54
  end
61
55
 
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
56
+ # @return [String] valores formatados bigquery.etht parte1
57
+ def etht_val1(hes)
58
+ "#{Integer(hes['blockNumber'])}," \
59
+ "#{Integer(hes['timeStamp'])}," \
60
+ "'#{hes['hash']}'," \
61
+ "#{Integer(hes['nonce'])}," \
62
+ "'#{hes['blockHash']}'," \
63
+ "#{Integer(hes['transactionIndex'])}," \
64
+ "'#{hes['from']}'," \
65
+ "'#{hes['to']}'," \
66
+ "#{etht_val2(hes)}"
67
+ end
68
+
69
+ # @return [String] valores formatados bigquery.etht parte2
70
+ def etht_val2(hes)
71
+ "cast('#{hes['value']}' as numeric)," \
72
+ "cast('#{hes['gas']}' as numeric)," \
73
+ "cast('#{hes['gasPrice']}' as numeric)," \
74
+ "#{Integer(hes['isError'])}," \
75
+ "#{hes['txreceipt_status'].length.zero? ? 'null' : hes['txreceipt_status']}," \
76
+ "#{hes['input'].length.zero? ? 'null' : "'#{hes['input']}'"}," \
77
+ "#{etht_val3(hes)}"
69
78
  end
70
79
 
71
- # @return [String] parte sql para processamento linhas existentes
72
- def sql_where
73
- "from hernanirvaz.coins.eos where blocknumber=#{row[0]}"
80
+ # @return [String] valores formatados bigquery.etht parte3
81
+ def etht_val3(hes)
82
+ "#{hes['contractAddress'].length.zero? ? 'null' : "'#{hes['contractAddress']}'"}," \
83
+ "0,cast('#{hes['gasUsed']}' as numeric),0," \
84
+ "#{Integer(ops[:h][hes['blockNumber']] || 0)}"
74
85
  end
75
86
 
76
- # @return [Integer] numero linhas inseridas
77
- def sql_insert
78
- return 1 unless linha[:i]
87
+ # @return [String] comando insert SQL formatado bigquery.ethk
88
+ def ethk_ins
89
+ "insert #{BD}.ethk(blocknumber,timestamp,txhash,nonce,blockhash,axfrom,contractaddress,axto,value,tokenname," \
90
+ 'tokensymbol,tokendecimal,transactionindex,gas,gasprice,gasused,cumulativegasused,input,confirmations,dias' \
91
+ ") VALUES(#{carteiras.novaesk.map { |e| ethk_val1(e) }.join(',')})"
92
+ end
93
+
94
+ # @return [String] valores formatados bigquery.ethk parte1
95
+ def ethk_val1(hes)
96
+ "#{Integer(hes['blockNumber'])}," \
97
+ "#{Integer(hes['timeStamp'])}," \
98
+ "'#{hes['hash']}'," \
99
+ "#{Integer(hes['nonce'])}," \
100
+ "'#{hes['blockHash']}'," \
101
+ "'#{hes['from']}'," \
102
+ "#{ethk_val2(hes)}"
103
+ end
79
104
 
80
- dml('insert hernanirvaz.coins.eos(' + eos_fields + ') VALUES(' + str_insert1)
105
+ # @return [String] valores formatados bigquery.ethk parte2
106
+ def ethk_val2(hes)
107
+ "#{hes['contractAddress'].length.zero? ? 'null' : "'#{hes['contractAddress']}'"}," \
108
+ "'#{hes['to']}'," \
109
+ "cast('#{hes['value']}' as numeric)," \
110
+ "'#{hes['tokenName']}'," \
111
+ "'#{hes['tokenSymbol']}'," \
112
+ "#{Integer(hes['tokenDecimal'])}," \
113
+ "#{Integer(hes['transactionIndex'])}," \
114
+ "#{ethk_val3(hes)}"
81
115
  end
82
116
 
83
- # @return [String] campos da tabela no bigquery
84
- def eos_fields
85
- 'blocknumber,time,contract,action,acfrom,acto,amount,symbol,memo,data,dias'
117
+ # @return [String] valores formatados bigquery.ethk parte3
118
+ def ethk_val3(hes)
119
+ "cast('#{hes['gas']}' as numeric)," \
120
+ "cast('#{hes['gasPrice']}' as numeric)," \
121
+ "cast('#{hes['gasUsed']}' as numeric),0," \
122
+ "#{hes['input'].length.zero? ? 'null' : "'#{hes['input']}'"},0," \
123
+ "#{Integer(ops[:h][hes['blockNumber']] || 0)}"
86
124
  end
87
125
 
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
126
+ # cria job bigquery & verifica execucao
127
+ #
128
+ # @param [String] sql a executar
129
+ # @return [Boolean] job ok?
130
+ def job?(sql)
131
+ @job = api.query_job(sql)
132
+ @job.wait_until_done!
133
+ puts(@job.error['message']) if @job.failed?
134
+ @job.failed?
91
135
  end
92
136
 
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)"
137
+ # cria Data Manipulation Language (DML) job bigquery
138
+ #
139
+ # @param (see job?)
140
+ # @return [Integer] numero linhas afetadas
141
+ def dml(sql)
142
+ job?(sql) ? 0 : job.num_dml_affected_rows
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 (see job?)
148
+ # @return [Array<Hash>] resultados do SQL
149
+ def sql(sql)
150
+ job?(sql) ? [] : job.data
101
151
  end
102
152
  end
103
153
  end
@@ -0,0 +1,126 @@
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 dadoses
29
+ @dadoses ||= api.multi_address_balance(dbq[:wb].map { |c| c[:ax] }).map { |e| base_etherscan(e) }
30
+ end
31
+
32
+ # @return [Array<Integer>] lista blocknumbers novos de transacoes normais
33
+ def novobnt
34
+ @novobnt ||= (dadoses.map { |e| e[:tx].map { |n| Integer(n['blockNumber']) } }.flatten -
35
+ dbq[:nt].map { |t| t[:blocknumber] }).uniq
36
+ end
37
+
38
+ # @return [Array<Integer>] lista blocknumbers novos de transacoes token
39
+ def novobnk
40
+ @novobnk ||= (dadoses.map { |e| e[:kx].map { |n| Integer(n['blockNumber']) } }.flatten -
41
+ dbq[:nk].map { |t| t[:blocknumber] }).uniq
42
+ end
43
+
44
+ # @return [Array<Hash>] lista transacoes normais novas
45
+ def novaest
46
+ @novaest ||= dadoses.map { |e| e[:tx].select { |s| novobnt.include?(Integer(s['blockNumber'])) } }.flatten.uniq
47
+ end
48
+
49
+ # @return [Array<Hash>] lista transacoes tokan novas
50
+ def novaesk
51
+ @novaesk ||= dadoses.map { |e| e[:kx].select { |s| novobnk.include?(Integer(s['blockNumber'])) } }.flatten.uniq
52
+ end
53
+
54
+ # @return [Array<Hash>] totdos dados juntos bigquery & etherscan
55
+ def dadosjn
56
+ @dadosjn ||= dbq[:wb].map { |b| bigquery_etherscan(b, dadoses.select { |s| b[:ax] == s[:ax] }.first) }
57
+ end
58
+
59
+ # @return [Array<Hash>] lista ordenada transacoes normais novas
60
+ def sortest
61
+ novaest.sort { |a, b| Integer(a['blockNumber']) <=> Integer(b['blockNumber']) }
62
+ end
63
+
64
+ # @return [Array<Hash>] lista ordenada transacoes tokan novas
65
+ def sortesk
66
+ novaesk.sort { |a, b| Integer(a['blockNumber']) <=> Integer(b['blockNumber']) }
67
+ end
68
+
69
+ # @return [Array<Hash>] lista ordenada transacoes normais & tokan novas
70
+ def totalha
71
+ (novaest + novaesk).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 [String] add endereco da carteira
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 (see etherscan_tx)
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
+
121
+ # @return [Boolean] carteira tem transacoes novas(sim=NOK, nao=OK)?
122
+ def ok?(hjn)
123
+ hjn[:bs] == hjn[:es] && hjn[:bt].count == hjn[:et].count && hjn[:bk].count == hjn[:ek].count
124
+ end
125
+ end
126
+ 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,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @author Hernani Rodrigues Vaz
4
+ module Etht
5
+ # (see Carteiras)
6
+ class Carteiras
7
+ # @return [String] texto formatado valores duma carteira
8
+ def valores(hjn)
9
+ format(
10
+ '%<v1>10.6f %<n1>2i %<n3>2i %<v2>11.6f %<n2>2i %<n4>2i %<ok>-3s',
11
+ v1: hjn[:bs],
12
+ n1: hjn[:bt].count,
13
+ n3: hjn[:bk].count,
14
+ v2: hjn[:es],
15
+ n2: hjn[:et].count,
16
+ n4: hjn[:ek].count,
17
+ ok: ok?(hjn) ? 'OK' : 'NOK'
18
+ )
19
+ end
20
+
21
+ # @return [String] texto formatado transacao normal
22
+ def formata_tx(htx)
23
+ format(
24
+ '%<bn>9i %<fr>-20.20s %<to>-20.20s %<dt>10.10s %<vl>17.6f',
25
+ bn: htx['blockNumber'],
26
+ fr: ftx(htx['from'], 20),
27
+ to: ftx(htx['to'], 20),
28
+ dt: Time.at(Integer(htx['timeStamp'])),
29
+ vl: (htx['value'].to_d / 10**18).round(10)
30
+ )
31
+ end
32
+
33
+ # @return [String] texto formatado transacao token
34
+ def formata_kx(hkx)
35
+ format(
36
+ '%<bn>9i %<fr>-20.20s %<to>-20.20s %<dt>10.10s %<vl>11.3f %<sy>-5.5s',
37
+ bn: hkx['blockNumber'],
38
+ fr: ftx(hkx['from'], 20),
39
+ to: ftx(hkx['to'], 20),
40
+ dt: Time.at(Integer(hkx['timeStamp'])),
41
+ vl: (hkx['value'].to_d / 10**18).round(10),
42
+ sy: hkx['tokenSymbol']
43
+ )
44
+ end
45
+
46
+ # @return [String] texto lista carteiras & lista transacoes & ajuste dias
47
+ def mostra
48
+ return unless dadosjn.count.positive?
49
+
50
+ puts("\nid address ----bigquery---- ----etherscan----")
51
+ dadosjn.each { |e| puts format('%<s1>-6.6s %<s2>-34.34s ', s1: e[:id], s2: ftx(e[:ax], 34)) + valores(e) }
52
+ mostra_tx
53
+ mostra_kx
54
+ mostra_ha
55
+ end
56
+
57
+ # @example ether address inicio..fim
58
+ # 0x10f3a0cf0b534c..c033cf32e8a03586
59
+ # @param [String] add ether address
60
+ # @param [Integer] max chars a mostrar
61
+ # @return [String] ether address formatada
62
+ def ftx(add, max)
63
+ i = Integer((max - 2) / 2)
64
+ max < 7 ? 'erro' : "#{add[0, i]}..#{add[-i..]}"
65
+ end
66
+
67
+ # @return [String] lista transacoes normais
68
+ def mostra_tx
69
+ return unless novaest.count.positive? && ops[:v]
70
+
71
+ puts("\ntx normal address from address to ---data--- ------valor------")
72
+ sortest.each { |e| puts formata_tx(e) }
73
+ end
74
+
75
+ # @return [String] lista transacoes token
76
+ def mostra_kx
77
+ return unless novaesk.count.positive? && ops[:v]
78
+
79
+ puts("\ntx token address from address to ---data--- ---valor--- token")
80
+ sortesk.each { |e| puts formata_kx(e) }
81
+ end
82
+
83
+ # @return [String] texto configuracao ajuste dias
84
+ def mostra_ha
85
+ return unless (novaest.count + novaesk.count).positive? && ops[:v]
86
+
87
+ puts("\nstring ajuste dias\n-h=#{totalha.map { |e| "#{e['blockNumber']}:0" }.join(' ')}")
88
+ end
89
+ end
90
+ 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.5'
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.5
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-20 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: