cns 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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