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 +4 -4
- data/.rubocop.yml +12 -0
- data/Gemfile.lock +4 -6
- data/README.md +6 -16
- data/etht.gemspec +13 -11
- data/exe/etht +4 -1
- data/lib/etht.rb +23 -15
- data/lib/etht/bigquery.rb +126 -72
- data/lib/etht/carteiras.rb +121 -0
- data/lib/etht/etherscan.rb +129 -0
- data/lib/etht/formatar.rb +110 -0
- data/lib/etht/version.rb +1 -1
- metadata +23 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 251284e1797817117272d9df42dc49b7a13deae5107445ca0488bd953d445b0f
|
4
|
+
data.tar.gz: 3f63d4fd81f939242e9cb5cd0543f26c515a1621c65a8704995fceaccfbfadc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88873cd75be80f1b78cb760a4e643a394a16ff5fb01ee0328273e5684e19944ba7c5f344f3fd63488e009a0c2d79976bd7d0994b994062b9cf63f1c6c990f4bf
|
7
|
+
data.tar.gz: b3991969370592a9ecfc8e969f7b4205b475aae29aca5029e1c005217416e1380a1a5bed8cc5c69e4efd5612332791b8eb3376a311e3d02bba6e891810a2320a
|
data/.rubocop.yml
ADDED
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
etht (0.1.
|
5
|
-
|
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.
|
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
|
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
|
-
|
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/
|
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/
|
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).
|
data/etht.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
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
|
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 =
|
24
|
-
|
25
|
-
|
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
|
31
|
-
spec.add_development_dependency
|
31
|
+
spec.add_development_dependency('bundler')
|
32
|
+
spec.add_development_dependency('rake')
|
32
33
|
|
33
|
-
spec.add_dependency
|
34
|
-
spec.add_dependency
|
35
|
-
spec.add_dependency
|
36
|
-
spec.add_dependency
|
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
data/lib/etht.rb
CHANGED
@@ -1,29 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
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
|
-
|
10
|
-
|
11
|
-
|
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
|
-
#
|
20
|
+
# classe para carregar/mostrar dados comuns bigquery & etherscan
|
14
21
|
class CLI < Thor
|
15
|
-
desc 'work', 'carrega
|
16
|
-
option :
|
17
|
-
|
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(
|
26
|
+
Bigquery.new(options).processa
|
21
27
|
end
|
22
28
|
|
23
|
-
desc 'show', 'mostra
|
24
|
-
|
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.
|
34
|
+
Bigquery.new(options).carteiras.mostra_resumo
|
27
35
|
end
|
28
36
|
|
29
37
|
default_task :show
|
data/lib/etht/bigquery.rb
CHANGED
@@ -1,105 +1,159 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
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
|
-
|
13
|
-
DI = '%Y-%m-%d %H:%M:%S'
|
8
|
+
BD = 'hernanirvaz.coins'
|
14
9
|
|
15
|
-
#
|
10
|
+
# classe para processar etherscan & bigquery
|
16
11
|
class Bigquery
|
17
12
|
# @return [Google::Cloud::Bigquery] API bigquery
|
18
|
-
attr_reader :
|
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
|
29
|
-
attr_reader :
|
30
|
-
|
31
|
-
# @param [
|
32
|
-
# @
|
33
|
-
# @option
|
34
|
-
# @option
|
35
|
-
# @
|
36
|
-
|
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
|
-
@
|
41
|
-
@
|
42
|
-
@linha = ops
|
27
|
+
@api = Google::Cloud::Bigquery.new
|
28
|
+
@ops = pop
|
43
29
|
end
|
44
30
|
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
#
|
57
|
-
|
58
|
-
|
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
|
-
#
|
65
|
-
|
66
|
-
|
67
|
-
|
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]
|
74
|
-
def
|
75
|
-
"
|
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 [
|
79
|
-
def
|
80
|
-
|
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
|
-
|
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]
|
86
|
-
def
|
87
|
-
'
|
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]
|
91
|
-
def
|
92
|
-
"#{
|
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]
|
96
|
-
def
|
97
|
-
"
|
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 [
|
101
|
-
def
|
102
|
-
|
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
|
data/lib/etht/version.rb
CHANGED
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
|
+
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-
|
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:
|
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
|
98
|
-
|
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:
|