cns 0.1.0 → 0.1.1

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.
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('openssl')
4
+ require('base64')
5
+ require('curb')
6
+ require('json')
7
+
8
+ # @author Hernani Rodrigues Vaz
9
+ module Cns
10
+ # classe para processar dados no kraken
11
+ class Apius
12
+ # @return [String] API key
13
+ attr_reader :aky
14
+ # @return [String] API secret
15
+ attr_reader :asc
16
+ # @return [String] API url base
17
+ attr_reader :urb
18
+ # @return [String] API private path
19
+ attr_reader :pth
20
+
21
+ # @param [String] pky API key
22
+ # @param [String] psc API secret
23
+ # @param [Hash] ops parametrizacao base da API
24
+ # @return [Apius] API kraken base
25
+ def initialize(
26
+ pky: ENV['KRAKEN_API_KEY'],
27
+ psc: ENV['KRAKEN_API_SECRET'],
28
+ ops: { www: 'https://api.kraken.com', ver: 0 }
29
+ )
30
+ @aky = pky
31
+ @asc = psc
32
+ @urb = "#{ops[:www]}/#{ops[:ver]}"
33
+ @pth = "/#{ops[:ver]}/private/"
34
+ end
35
+
36
+ # @example
37
+ # {
38
+ # error: [],
39
+ # result: {
40
+ # ZEUR: '0.0038',
41
+ # XXBT: '0.0000000000',
42
+ # XETH: '1.0000000000',
43
+ # XETC: '0.0000000000',
44
+ # EOS: '0.0000001700',
45
+ # BCH: '0.0000000000'
46
+ # }
47
+ # }
48
+ # @return [Hash] saldos no kraken
49
+ def account
50
+ api_post('Balance')[:result]
51
+ end
52
+
53
+ # @example
54
+ # {
55
+ # error: [],
56
+ # result: {
57
+ # trades: {
58
+ # "TVINF5-TIOUB-YFNGKE": {
59
+ # ordertxid: 'ORPSUW-YKP4F-UJZOC6',
60
+ # pair: 'XETHXXBT',
61
+ # time: 1_463_435_684.8387,
62
+ # type: 'buy',
63
+ # ordertype: 'market',
64
+ # price: '0.024989',
65
+ # cost: '1.193973',
66
+ # fee: '0.003104',
67
+ # vol: '47.77994129',
68
+ # margin: '0.000000',
69
+ # misc: ''
70
+ # },
71
+ # "OUTRO-TRADE-ID": {}
72
+ # },
73
+ # count: 157
74
+ # }
75
+ # }
76
+ # @param [Integer] ofs offset dos dados a obter
77
+ # @param [Hash] has acumulador dos dados a obter
78
+ # @return [Hash] dados trades no kraken
79
+ def trades(ofs = 0, has = {})
80
+ r = api_post('TradesHistory', ofs: ofs)[:result]
81
+ has.merge!(r[:trades])
82
+ ofs += 50
83
+ ofs < r[:count] ? trades(ofs, has) : has
84
+ rescue StandardError
85
+ has
86
+ end
87
+
88
+ # @example
89
+ # {
90
+ # error: [],
91
+ # result: {
92
+ # ledger: {
93
+ # "LXXURB-ITI7S-CXVERS": {
94
+ # refid: 'ACCHF3A-RIBBMO-VYBESY',
95
+ # time: 1_543_278_716.2775,
96
+ # type: 'withdrawal',
97
+ # subtype: '',
98
+ # aclass: 'currency',
99
+ # asset: 'ZEUR',
100
+ # amount: '-15369.6200',
101
+ # fee: '0.0900',
102
+ # balance: '0.0062'
103
+ # },
104
+ # "OUTRO-LEDGER-ID": {}
105
+ # },
106
+ # count: 376
107
+ # }
108
+ # }
109
+ # @param (see trades)
110
+ # @return [Hash] dados ledger no kraken
111
+ def ledger(ofs = 0, has = {})
112
+ r = api_post('Ledgers', ofs: ofs)[:result]
113
+ has.merge!(r[:ledger])
114
+ ofs += 50
115
+ ofs < r[:count] ? ledger(ofs, has) : has
116
+ rescue StandardError
117
+ has
118
+ end
119
+
120
+ private
121
+
122
+ # HTTP GET request for public kraken API queries.
123
+ def api_get(uri, **ops)
124
+ resposta(Curl.get("#{urb}/public/#{uri}", ops))
125
+ end
126
+
127
+ # HTTP POST request for private kraken API queries involving user credentials.
128
+ def api_post(uri, **ops)
129
+ # continually-increasing unsigned integer nonce from the current Unix Time
130
+ ops.merge!({ nonce: Integer(Float(Time.now) * 1e6) })
131
+
132
+ resposta(Curl.post("#{urb}/private/#{uri}", ops) { |r| r.headers = hdrs(uri, ops) })
133
+ end
134
+
135
+ # @return [Hash] headers necessarios para pedido HTTP
136
+ def hdrs(qry, ops)
137
+ {
138
+ 'api-key': aky,
139
+ 'api-sign': auth(qry, ops[:nonce], URI.encode_www_form(ops))
140
+ }
141
+ end
142
+
143
+ # @return [String] assinarura codificada dos pedidos HTTP
144
+ def auth(qry, non, par)
145
+ raise(ArgumentError, 'API Key is not set') unless aky
146
+ raise(ArgumentError, 'API Secret is not set') unless asc
147
+
148
+ Base64.strict_encode64(
149
+ OpenSSL::HMAC.digest('sha512', Base64.decode64(asc), [pth, qry, Digest::SHA256.digest("#{non}#{par}")].join)
150
+ )
151
+ end
152
+
153
+ # @return [Hash] resposta do pedido HTTP
154
+ def resposta(http)
155
+ http.response_code == 200 ? JSON.parse(http.body, symbolize_names: true) : http.status
156
+ rescue JSON::ParserError,
157
+ EOFError,
158
+ Errno::ECONNRESET,
159
+ Errno::EINVAL,
160
+ Net::HTTPBadResponse,
161
+ Net::HTTPHeaderSyntaxError,
162
+ Net::ProtocolError,
163
+ Timeout::Error => e
164
+ "Erro da API kraken #{e.inspect}"
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('google/cloud/bigquery')
4
+ require('bigdecimal/util')
5
+
6
+ # @author Hernani Rodrigues Vaz
7
+ module Cns
8
+ BD = 'hernanirvaz.coins'
9
+
10
+ # classe para processar bigquery
11
+ class Bigquery
12
+ # @return [Google::Cloud::Bigquery] API bigquery
13
+ attr_reader :api
14
+ # @return [Google::Cloud::Bigquery::QueryJob] job bigquery
15
+ attr_reader :job
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 trades & ledger?
24
+ # @option pop [Boolean] :t (false) mostra transacoes todas ou somente novas?
25
+ # @return [Bigquery] API bigquery & kraken/bitcoinde/paymium/therock
26
+ def initialize(pop)
27
+ # usa env GOOGLE_APPLICATION_CREDENTIALS para obter credentials
28
+ # @see https://cloud.google.com/bigquery/docs/authentication/getting-started
29
+ @api = Google::Cloud::Bigquery.new
30
+ @ops = pop
31
+ end
32
+
33
+ # situacao completa entre kraken/bitcoinde/paymium/therock & bigquery
34
+ def mostra_tudo
35
+ apius.mostra_resumo
36
+ apide.mostra_resumo
37
+ apifr.mostra_resumo
38
+ apimt.mostra_resumo
39
+ apies.mostra_resumo
40
+ apigm.mostra_resumo
41
+ end
42
+
43
+ # insere (caso existam) transacoes novas do kraken/bitcoinde/paymium/therock no bigquery
44
+ def processa_tudo
45
+ processa_us
46
+ processa_de
47
+ processa_fr
48
+ processa_mt
49
+ processa_eos
50
+ processa_eth
51
+ end
52
+
53
+ # insere transacoes kraken novas nas tabelas ust (trades), usl (ledger)
54
+ def processa_us
55
+ puts(format("%<n>2i TRADES KRAKEN INSERIDAS #{BD}.ust", n: apius.trades.empty? ? 0 : dml(ust_ins)))
56
+ puts(format("%<n>2i LEDGER KRAKEN INSERIDAS #{BD}.usl", n: apius.ledger.empty? ? 0 : dml(usl_ins)))
57
+ end
58
+
59
+ # insere transacoes bitcoinde novas nas tabelas det (trades), del (ledger)
60
+ def processa_de
61
+ puts(format("%<n>2i TRADES BITCOINDE INSERIDAS #{BD}.det", n: apide.trades.empty? ? 0 : dml(det_ins)))
62
+ puts(format("%<n>2i LEDGER BITCOINDE INSERIDAS #{BD}.del", n: apide.ledger.empty? ? 0 : dml(del_ins)))
63
+ end
64
+
65
+ # insere transacoes paymium novas na tabela fr (ledger)
66
+ def processa_fr
67
+ puts(format("%<n>2i LEDGER PAYMIUM INSERIDAS #{BD}.fr", n: apifr.ledger.empty? ? 0 : dml(frl_ins)))
68
+ end
69
+
70
+ # insere transacoes paymium novas na tabela mt (ledger)
71
+ def processa_mt
72
+ puts(format("%<n>2i LEDGER THEROCK INSERIDAS #{BD}.mt", n: apimt.ledger.empty? ? 0 : dml(mtl_ins)))
73
+ end
74
+
75
+ # insere transacoes novas na tabela eos
76
+ def processa_eos
77
+ puts(format("%<n>2i LINHAS INSERIDAS #{BD}.eos ", n: apigm.novax.count.positive? ? dml(eost_ins) : 0))
78
+ end
79
+
80
+ # insere transacoes novas nas tabelas etht (trx normais), ethk (trx token)
81
+ def processa_eth
82
+ puts(format("%<n>2i LINHAS INSERIDAS #{BD}.etht", n: apies.novtx.count.positive? ? dml(etht_ins) : 0))
83
+ puts(format("%<n>2i LINHAS INSERIDAS #{BD}.ethk", n: apies.novkx.count.positive? ? dml(ethk_ins) : 0))
84
+ end
85
+
86
+ # cria job bigquery & verifica execucao
87
+ #
88
+ # @param cmd (see sql)
89
+ # @return [Boolean] job ok?
90
+ def job?(cmd)
91
+ @job = api.query_job(cmd)
92
+ @job.wait_until_done!
93
+ puts(@job.error['message']) if @job.failed?
94
+ @job.failed?
95
+ end
96
+
97
+ # cria Structured Query Language (SQL) job bigquery
98
+ #
99
+ # @param [String] cmd comando SQL a executar
100
+ # @param [String] res resultado quando SQL tem erro
101
+ # @return [Google::Cloud::Bigquery::Data] resultado do SQL
102
+ def sql(cmd, res = [])
103
+ @sqr = job?(cmd) ? res : job.data
104
+ end
105
+
106
+ # cria Data Manipulation Language (DML) job bigquery
107
+ #
108
+ # @param cmd (see sql)
109
+ # @return [Integer] numero linhas afetadas
110
+ def dml(cmd)
111
+ job?(cmd) ? 0 : job.num_dml_affected_rows
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @author Hernani Rodrigues Vaz
4
+ module Cns
5
+ # (see Bigquery)
6
+ class Bigquery
7
+ # @return [Etherscan] API etherscan
8
+ def apies
9
+ @apies ||= Etherscan.new(
10
+ {
11
+ wb: sql("select * from #{BD}.walletEth order by 2"),
12
+ nt: sql("select itx,iax from #{BD}.ethtx"),
13
+ nk: sql("select itx,iax from #{BD}.ethkx")
14
+ },
15
+ ops
16
+ )
17
+ end
18
+
19
+ # @return [Greymass] API greymass
20
+ def apigm
21
+ @apigm ||= Greymass.new(
22
+ {
23
+ wb: sql("select * from #{BD}.walletEos order by 2"),
24
+ nt: sql("select itx,iax from #{BD}.eostx")
25
+ },
26
+ ops
27
+ )
28
+ end
29
+
30
+ # @return [Kraken] API kraken - obter saldos & transacoes trades e ledger
31
+ def apius
32
+ @apius ||= Kraken.new(
33
+ {
34
+ sl: sql("select sum(btc) xxbt,sum(eth) xeth,sum(eos) eos,sum(eur) zeur from #{BD}.ussl")[0],
35
+ nt: sql("select * from #{BD}.ustx order by time,txid"),
36
+ nl: sql("select * from #{BD}.uslx order by time,txid")
37
+ },
38
+ ops
39
+ )
40
+ end
41
+
42
+ # @return [Bitcoinde] API Bitcoinde - obter saldos & transacoes trades e ledger
43
+ def apide
44
+ @apide ||= Bitcoinde.new(
45
+ {
46
+ sl: sql("select sum(btc) btc from #{BD}.desl")[0],
47
+ nt: sql("select * from #{BD}.detx order by time,txid"),
48
+ nl: sql("select * from #{BD}.delx order by time,txid")
49
+ },
50
+ ops
51
+ )
52
+ end
53
+
54
+ # @return [Paymium] API Paymium - obter saldos & transacoes ledger
55
+ def apifr
56
+ @apifr ||= Paymium.new(
57
+ {
58
+ sl: sql("select sum(btc) btc,sum(eur) eur from #{BD}.frsl")[0],
59
+ nl: sql("select * from #{BD}.frlx order by time,txid")
60
+ },
61
+ ops
62
+ )
63
+ end
64
+
65
+ # @return [TheRock] API TheRock - obter saldos & transacoes ledger
66
+ def apimt
67
+ @apimt ||= TheRock.new(
68
+ {
69
+ sl: sql("select sum(btc) btc,sum(eur) eur from #{BD}.mtsl")[0],
70
+ nl: sql("select * from #{BD}.mtlx order by time,txid")
71
+ },
72
+ ops
73
+ )
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('google/cloud/bigquery')
4
+ require('bigdecimal/util')
5
+
6
+ # @author Hernani Rodrigues Vaz
7
+ module Cns
8
+ # classe para processar etherscan/greymass & bigquery
9
+ class Bigquery
10
+ # @return [String] comando insert SQL formatado etht (trx normais)
11
+ def etht_ins
12
+ "insert #{BD}.etht(blocknumber,timestamp,txhash,nonce,blockhash,transactionindex,axfrom,axto,iax," \
13
+ 'value,gas,gasprice,gasused,iserror,txreceipt_status,input,contractaddress,dias' \
14
+ ") VALUES#{apies.novtx.map { |e| etht_val1(e) }.join(',')}"
15
+ end
16
+
17
+ # @return [String] valores formatados etht (trx normais parte1)
18
+ def etht_val1(htx)
19
+ "(#{Integer(htx[:blockNumber])}," \
20
+ "#{Integer(htx[:timeStamp])}," \
21
+ "'#{htx[:hash]}'," \
22
+ "#{Integer(htx[:nonce])}," \
23
+ "'#{htx[:blockHash]}'," \
24
+ "#{Integer(htx[:transactionIndex])}," \
25
+ "'#{htx[:from]}'," \
26
+ "'#{htx[:to]}'," \
27
+ "'#{htx[:iax]}'," \
28
+ "#{etht_val2(htx)}"
29
+ end
30
+
31
+ # @return [String] valores formatados etht (trx normais parte2)
32
+ def etht_val2(htx)
33
+ "cast('#{htx[:value]}' as numeric)," \
34
+ "cast('#{htx[:gas]}' as numeric)," \
35
+ "cast('#{htx[:gasPrice]}' as numeric)," \
36
+ "cast('#{htx[:gasUsed]}' as numeric)," \
37
+ "#{Integer(htx[:isError])}," \
38
+ "#{htx[:txreceipt_status].length.zero? ? 'null' : htx[:txreceipt_status]}," \
39
+ "#{etht_val3(htx)}"
40
+ end
41
+
42
+ # @return [String] valores formatados etht (trx normais parte3)
43
+ def etht_val3(htx)
44
+ "#{htx[:input].length.zero? ? 'null' : "'#{htx[:input]}'"}," \
45
+ "#{htx[:contractAddress].length.zero? ? 'null' : "'#{htx[:contractAddress]}'"}," \
46
+ "#{Integer(ops[:h][htx[:blockNumber]] || 0)})"
47
+ end
48
+
49
+ # @return [String] comando insert SQL formatado ethk (trx token)
50
+ def ethk_ins
51
+ "insert #{BD}.ethk(blocknumber,timestamp,txhash,nonce,blockhash,transactionindex,axfrom,axto,iax," \
52
+ 'value,tokenname,tokensymbol,tokendecimal,gas,gasprice,gasused,input,contractaddress,dias' \
53
+ ") VALUES#{apies.novkx.map { |e| ethk_val1(e) }.join(',')}"
54
+ end
55
+
56
+ # @return [String] valores formatados ethk (trx token parte1)
57
+ def ethk_val1(hkx)
58
+ "(#{Integer(hkx[:blockNumber])}," \
59
+ "#{Integer(hkx[:timeStamp])}," \
60
+ "'#{hkx[:hash]}'," \
61
+ "#{Integer(hkx[:nonce])}," \
62
+ "'#{hkx[:blockHash]}'," \
63
+ "#{Integer(hkx[:transactionIndex])}," \
64
+ "'#{hkx[:from]}'," \
65
+ "'#{hkx[:to]}'," \
66
+ "'#{hkx[:iax]}'," \
67
+ "#{ethk_val2(hkx)}"
68
+ end
69
+
70
+ # @return [String] valores formatados ethk (trx token parte2)
71
+ def ethk_val2(hkx)
72
+ "cast('#{hkx[:value]}' as numeric)," \
73
+ "'#{hkx[:tokenName]}'," \
74
+ "'#{hkx[:tokenSymbol]}'," \
75
+ "#{Integer(hkx[:tokenDecimal])}," \
76
+ "cast('#{hkx[:gas]}' as numeric)," \
77
+ "cast('#{hkx[:gasPrice]}' as numeric)," \
78
+ "cast('#{hkx[:gasUsed]}' as numeric)," \
79
+ "#{ethk_val3(hkx)}"
80
+ end
81
+
82
+ # @return [String] valores formatados ethk (trx token parte3)
83
+ def ethk_val3(hkx)
84
+ "#{hkx[:input].length.zero? ? 'null' : "'#{hkx[:input]}'"}," \
85
+ "#{hkx[:contractAddress].length.zero? ? 'null' : "'#{hkx[:contractAddress]}'"}," \
86
+ "#{Integer(ops[:h][hkx[:blockNumber]] || 0)})"
87
+ end
88
+
89
+ # @return [String] comando insert SQL formatado eos
90
+ def eost_ins
91
+ "insert #{BD}.eos(gseq,aseq,bnum,time,contract,action,acfrom,acto,iax,amount,moeda,memo,dias" \
92
+ ") VALUES#{apigm.novax.map { |e| eost_val1(e) }.join(',')}"
93
+ end
94
+
95
+ # @param [Hash] htx transacao ligadas a uma carteira - sem elementos irrelevantes
96
+ # @return [String] valores formatados para insert eos (parte1)
97
+ def eost_val1(htx)
98
+ a = htx[:action_trace][:act]
99
+ "(#{htx[:global_action_seq]}," \
100
+ "#{htx[:account_action_seq]}," \
101
+ "#{htx[:block_num]}," \
102
+ "DATETIME(TIMESTAMP('#{htx[:block_time]}'))," \
103
+ "'#{a[:account]}'," \
104
+ "'#{a[:name]}'," \
105
+ "#{eost_val2(htx, a)}"
106
+ end
107
+
108
+ # @param [Hash] htx transacao ligadas a uma carteira - sem elementos irrelevantes
109
+ # @return [String] valores formatados para insert eos (parte2)
110
+ def eost_val2(htx, act)
111
+ d = act[:data]
112
+ q = d[:quantity].to_s
113
+ s = d[:memo].inspect
114
+ "'#{d[:from]}'," \
115
+ "'#{d[:to]}'," \
116
+ "'#{htx[:iax]}'," \
117
+ "#{q.to_d},'#{q[/[[:upper:]]+/]}'," \
118
+ "nullif('#{s.gsub(/['"]/, '')}','nil')," \
119
+ "#{ops[:h][String(htx[:itx])] || 0})"
120
+ end
121
+ end
122
+ end