coinsync 0.1.0
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 +7 -0
- data/MIT-LICENSE.txt +21 -0
- data/README.md +255 -0
- data/bin/coinsync +78 -0
- data/doc/importers.md +201 -0
- data/lib/coinsync.rb +18 -0
- data/lib/coinsync/balance.rb +19 -0
- data/lib/coinsync/balance_task.rb +65 -0
- data/lib/coinsync/build_task.rb +43 -0
- data/lib/coinsync/builder.rb +32 -0
- data/lib/coinsync/config.rb +94 -0
- data/lib/coinsync/crypto_classifier.rb +27 -0
- data/lib/coinsync/currencies.rb +35 -0
- data/lib/coinsync/currency_converter.rb +65 -0
- data/lib/coinsync/currency_converters/all.rb +1 -0
- data/lib/coinsync/currency_converters/base.rb +46 -0
- data/lib/coinsync/currency_converters/cache.rb +34 -0
- data/lib/coinsync/currency_converters/fixer.rb +36 -0
- data/lib/coinsync/currency_converters/nbp.rb +38 -0
- data/lib/coinsync/formatter.rb +44 -0
- data/lib/coinsync/import_task.rb +37 -0
- data/lib/coinsync/importers/all.rb +1 -0
- data/lib/coinsync/importers/ark_voting.rb +121 -0
- data/lib/coinsync/importers/base.rb +35 -0
- data/lib/coinsync/importers/binance_api.rb +210 -0
- data/lib/coinsync/importers/bitbay20.rb +144 -0
- data/lib/coinsync/importers/bitbay_api.rb +224 -0
- data/lib/coinsync/importers/bitcurex.rb +71 -0
- data/lib/coinsync/importers/bittrex_api.rb +81 -0
- data/lib/coinsync/importers/bittrex_csv.rb +75 -0
- data/lib/coinsync/importers/changelly.rb +57 -0
- data/lib/coinsync/importers/circle.rb +58 -0
- data/lib/coinsync/importers/default.rb +90 -0
- data/lib/coinsync/importers/etherdelta.rb +93 -0
- data/lib/coinsync/importers/kraken_api.rb +134 -0
- data/lib/coinsync/importers/kraken_common.rb +137 -0
- data/lib/coinsync/importers/kraken_csv.rb +28 -0
- data/lib/coinsync/importers/kucoin_api.rb +172 -0
- data/lib/coinsync/importers/lisk_voting.rb +110 -0
- data/lib/coinsync/outputs/all.rb +1 -0
- data/lib/coinsync/outputs/base.rb +32 -0
- data/lib/coinsync/outputs/list.rb +123 -0
- data/lib/coinsync/outputs/raw.rb +45 -0
- data/lib/coinsync/outputs/summary.rb +48 -0
- data/lib/coinsync/request.rb +31 -0
- data/lib/coinsync/run_command_task.rb +20 -0
- data/lib/coinsync/source.rb +43 -0
- data/lib/coinsync/source_filter.rb +10 -0
- data/lib/coinsync/table_printer.rb +29 -0
- data/lib/coinsync/transaction.rb +125 -0
- data/lib/coinsync/version.rb +3 -0
- metadata +95 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'csv'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
require_relative 'base'
|
6
|
+
require_relative '../currencies'
|
7
|
+
require_relative '../transaction'
|
8
|
+
|
9
|
+
module CoinSync
|
10
|
+
module Importers
|
11
|
+
class BitBay20 < Base
|
12
|
+
register_importer :bitbay20
|
13
|
+
|
14
|
+
OP_PAY_BUYING = 'Pay for buying currency'
|
15
|
+
OP_PAY_SELLING = 'Pay for selling currency'
|
16
|
+
OP_PURCHASE = 'Currency purchase'
|
17
|
+
OP_SALE = 'Currency sale'
|
18
|
+
OP_FEE = 'Transaction fee'
|
19
|
+
|
20
|
+
MAX_TIME_DIFFERENCE = 5.0
|
21
|
+
|
22
|
+
TRANSACTION_TYPES = [OP_PAY_BUYING, OP_PAY_SELLING, OP_PURCHASE, OP_SALE, OP_FEE]
|
23
|
+
|
24
|
+
class HistoryEntry
|
25
|
+
attr_accessor :date, :accounting_date, :type, :amount, :currency
|
26
|
+
|
27
|
+
def initialize(line)
|
28
|
+
# TODO: force parsing in Polish timezone
|
29
|
+
@date = Time.parse(line[0]) unless line[0] == '-'
|
30
|
+
@accounting_date = Time.parse(line[1]) unless line[1] == '-'
|
31
|
+
@type = line[2]
|
32
|
+
|
33
|
+
amount, currency = line[3].split(' ')
|
34
|
+
@amount = BigDecimal.new(amount.gsub(/,/, ''))
|
35
|
+
@currency = parse_currency(currency)
|
36
|
+
end
|
37
|
+
|
38
|
+
def crypto?
|
39
|
+
@currency.crypto?
|
40
|
+
end
|
41
|
+
|
42
|
+
def fiat?
|
43
|
+
@currency.fiat?
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_currency(code)
|
47
|
+
case code
|
48
|
+
when 'BTC' then CryptoCurrency.new('BTC')
|
49
|
+
when 'ETH' then CryptoCurrency.new('ETH')
|
50
|
+
when 'LSK' then CryptoCurrency.new('LSK')
|
51
|
+
when 'LTC' then CryptoCurrency.new('LTC')
|
52
|
+
when 'PLN' then FiatCurrency.new('PLN')
|
53
|
+
else raise "Unknown currency: #{code}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def read_transaction_list(source)
|
59
|
+
csv = CSV.new(source, col_sep: ';')
|
60
|
+
|
61
|
+
matching = []
|
62
|
+
transactions = []
|
63
|
+
|
64
|
+
csv.each do |line|
|
65
|
+
next if line.empty?
|
66
|
+
next if line[0] !~ /^\d/
|
67
|
+
|
68
|
+
entry = HistoryEntry.new(line)
|
69
|
+
|
70
|
+
next unless TRANSACTION_TYPES.include?(entry.type)
|
71
|
+
|
72
|
+
if !matching.empty? && matching.any? { |e| (e.date - entry.date).abs > MAX_TIME_DIFFERENCE }
|
73
|
+
if matching.any? { |e| e.type != OP_FEE }
|
74
|
+
raise "BitBay importer error: Couldn't match some history lines"
|
75
|
+
else
|
76
|
+
matching.clear
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
matching << entry
|
81
|
+
|
82
|
+
if matching.length == 3
|
83
|
+
matching.sort_by!(&:type)
|
84
|
+
types = matching.map(&:type)
|
85
|
+
time = matching.map(&:date).sort.last
|
86
|
+
|
87
|
+
if types == [OP_PURCHASE, OP_PAY_BUYING, OP_FEE] &&
|
88
|
+
matching[0].crypto? && matching[0].amount > 0 &&
|
89
|
+
matching[1].fiat? && matching[1].amount < 0 &&
|
90
|
+
matching[2].crypto? && matching[2].amount <= 0 &&
|
91
|
+
matching[0].currency == matching[2].currency
|
92
|
+
transactions << Transaction.new(
|
93
|
+
exchange: 'BitBay',
|
94
|
+
bought_currency: matching[0].currency,
|
95
|
+
sold_currency: matching[1].currency,
|
96
|
+
time: time,
|
97
|
+
bought_amount: matching[0].amount + matching[2].amount,
|
98
|
+
sold_amount: -matching[1].amount
|
99
|
+
)
|
100
|
+
elsif types == [OP_SALE, OP_PAY_SELLING, OP_FEE] &&
|
101
|
+
matching[0].crypto? && matching[0].amount < 0 &&
|
102
|
+
matching[1].fiat? && matching[1].amount > 0 &&
|
103
|
+
matching[2].fiat? && matching[2].amount <= 0 &&
|
104
|
+
matching[1].currency == matching[2].currency
|
105
|
+
transactions << Transaction.new(
|
106
|
+
exchange: 'BitBay',
|
107
|
+
bought_currency: matching[1].currency,
|
108
|
+
sold_currency: matching[0].currency,
|
109
|
+
time: time,
|
110
|
+
bought_amount: matching[1].amount + matching[2].amount,
|
111
|
+
sold_amount: -matching[0].amount
|
112
|
+
)
|
113
|
+
elsif types == [OP_PURCHASE, OP_SALE, OP_FEE] &&
|
114
|
+
matching[0].fiat? && matching[0].amount > 0 &&
|
115
|
+
matching[1].crypto? && matching[1].amount < 0 &&
|
116
|
+
matching[2].fiat? && matching[2].amount <= 0 &&
|
117
|
+
matching[0].currency == matching[2].currency
|
118
|
+
transactions << Transaction.new(
|
119
|
+
exchange: 'BitBay',
|
120
|
+
bought_currency: matching[0].currency,
|
121
|
+
sold_currency: matching[1].currency,
|
122
|
+
time: time,
|
123
|
+
bought_amount: matching[0].amount + matching[2].amount,
|
124
|
+
sold_amount: -matching[1].amount
|
125
|
+
)
|
126
|
+
else
|
127
|
+
raise "BitBay importer error: Couldn't match some history lines"
|
128
|
+
end
|
129
|
+
|
130
|
+
matching.clear
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
if !matching.empty?
|
135
|
+
if matching.any? { |l| l.type != OP_FEE }
|
136
|
+
raise "BitBay importer error: Couldn't match some history lines"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
transactions.reverse
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require 'openssl'
|
5
|
+
require 'time'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
require_relative 'base'
|
9
|
+
require_relative '../balance'
|
10
|
+
require_relative '../currencies'
|
11
|
+
require_relative '../request'
|
12
|
+
require_relative '../transaction'
|
13
|
+
|
14
|
+
module CoinSync
|
15
|
+
module Importers
|
16
|
+
class BitBayAPI < Base
|
17
|
+
register_importer :bitbay_api
|
18
|
+
|
19
|
+
BASE_URL = "https://bitbay.net/API/Trading/tradingApi.php"
|
20
|
+
|
21
|
+
OP_PURCHASE = '+currency_transaction'
|
22
|
+
OP_SALE = '-pay_for_currency'
|
23
|
+
OP_FEE = '-fee'
|
24
|
+
|
25
|
+
MAX_TIME_DIFFERENCE = 5.0
|
26
|
+
TRANSACTION_TYPES = [OP_PURCHASE, OP_SALE, OP_FEE]
|
27
|
+
|
28
|
+
class HistoryEntry
|
29
|
+
attr_accessor :date, :amount, :type, :currency
|
30
|
+
|
31
|
+
def initialize(hash)
|
32
|
+
@date = Time.parse(hash['time']) # TODO: these times are all fucked up
|
33
|
+
@amount = BigDecimal.new(hash['amount'])
|
34
|
+
@type = hash['operation_type']
|
35
|
+
@currency = parse_currency(hash['currency'])
|
36
|
+
end
|
37
|
+
|
38
|
+
def crypto?
|
39
|
+
@currency.crypto?
|
40
|
+
end
|
41
|
+
|
42
|
+
def fiat?
|
43
|
+
@currency.fiat?
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_currency(code)
|
47
|
+
case code.upcase
|
48
|
+
when 'BTC' then CryptoCurrency.new('BTC')
|
49
|
+
when 'ETH' then CryptoCurrency.new('ETH')
|
50
|
+
when 'LTC' then CryptoCurrency.new('LTC')
|
51
|
+
when 'LSK' then CryptoCurrency.new('LSK')
|
52
|
+
when 'BCC' then CryptoCurrency.new('BCH')
|
53
|
+
when 'BTG' then CryptoCurrency.new('BTG')
|
54
|
+
when 'GAME' then CryptoCurrency.new('GAME')
|
55
|
+
when 'DASH' then CryptoCurrency.new('DASH')
|
56
|
+
when 'PLN' then FiatCurrency.new('PLN')
|
57
|
+
when 'EUR' then FiatCurrency.new('EUR')
|
58
|
+
when 'USD' then FiatCurrency.new('USD')
|
59
|
+
else raise "Unknown currency: #{code}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(config, params = {})
|
65
|
+
super
|
66
|
+
|
67
|
+
# required permissions:
|
68
|
+
# * for balance checks:
|
69
|
+
# - "Crypto deposit" (shown as "Get and create cryptocurrency addresses" + "Funds deposit")
|
70
|
+
# - "Updating a wallets list" (shown as "Pobieranie rachunków")
|
71
|
+
# * for transaction history:
|
72
|
+
# - "History" (shown as "Fetch history of transactions")
|
73
|
+
|
74
|
+
@public_key = params['api_public_key']
|
75
|
+
@secret_key = params['api_private_key']
|
76
|
+
end
|
77
|
+
|
78
|
+
def can_import?(type)
|
79
|
+
@public_key && @secret_key && [:balances, :transactions].include?(type)
|
80
|
+
end
|
81
|
+
|
82
|
+
def import_transactions(filename)
|
83
|
+
info = fetch_info
|
84
|
+
|
85
|
+
currencies = info['balances'].keys
|
86
|
+
transactions = []
|
87
|
+
|
88
|
+
currencies.each do |currency|
|
89
|
+
sleep 1 # rate limiting
|
90
|
+
|
91
|
+
response = make_request('history', currency: currency, limit: 10000) # TODO: does this limit really work?
|
92
|
+
|
93
|
+
case response
|
94
|
+
when Net::HTTPSuccess
|
95
|
+
json = JSON.parse(response.body)
|
96
|
+
|
97
|
+
if !json.is_a?(Array)
|
98
|
+
raise "BitBay API importer: Invalid response: #{response.body}"
|
99
|
+
end
|
100
|
+
|
101
|
+
transactions.concat(json)
|
102
|
+
when Net::HTTPBadRequest
|
103
|
+
raise "BitBay API importer: Bad request: #{response}"
|
104
|
+
else
|
105
|
+
raise "BitBay API importer: Bad response: #{response}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
transactions.each_with_index { |tx, i| tx['i'] = i }
|
110
|
+
transactions.sort_by! { |tx| [tx['time'], -tx['i']] }
|
111
|
+
transactions.each_with_index { |tx, i| tx.delete('i') }
|
112
|
+
|
113
|
+
File.write(filename, JSON.pretty_generate(transactions))
|
114
|
+
end
|
115
|
+
|
116
|
+
def import_balances
|
117
|
+
info = fetch_info
|
118
|
+
|
119
|
+
info['balances'].select { |k, v|
|
120
|
+
v['available'].to_f > 0 || v['locked'].to_f > 0
|
121
|
+
}.map { |k, v|
|
122
|
+
Balance.new(
|
123
|
+
CryptoCurrency.new(k),
|
124
|
+
available: BigDecimal.new(v['available']),
|
125
|
+
locked: BigDecimal.new(v['locked'])
|
126
|
+
)
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
def read_transaction_list(source)
|
131
|
+
json = JSON.parse(source.read)
|
132
|
+
|
133
|
+
matching = []
|
134
|
+
transactions = []
|
135
|
+
|
136
|
+
json.each do |hash|
|
137
|
+
entry = HistoryEntry.new(hash)
|
138
|
+
|
139
|
+
next unless TRANSACTION_TYPES.include?(entry.type)
|
140
|
+
|
141
|
+
if !matching.empty? && matching.any? { |e| (e.date - entry.date).abs > MAX_TIME_DIFFERENCE }
|
142
|
+
transactions << process_matched(matching)
|
143
|
+
end
|
144
|
+
|
145
|
+
matching << entry
|
146
|
+
end
|
147
|
+
|
148
|
+
if !matching.empty?
|
149
|
+
transactions << process_matched(matching)
|
150
|
+
end
|
151
|
+
|
152
|
+
transactions
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def process_matched(matching)
|
159
|
+
if matching.length % 3 == 0
|
160
|
+
purchases = matching.select { |tx| tx.type == OP_PURCHASE }
|
161
|
+
sales = matching.select { |tx| tx.type == OP_SALE }
|
162
|
+
fees = matching.select { |tx| tx.type == OP_FEE }
|
163
|
+
|
164
|
+
if purchases.length == sales.length && purchases.length == fees.length
|
165
|
+
bought_currency = (purchases + fees).map(&:currency).uniq
|
166
|
+
sold_currency = sales.map(&:currency).uniq
|
167
|
+
|
168
|
+
if bought_currency.length == 1 && sold_currency.length == 1
|
169
|
+
matching.clear
|
170
|
+
|
171
|
+
return Transaction.new(
|
172
|
+
exchange: 'BitBay',
|
173
|
+
bought_currency: bought_currency.first,
|
174
|
+
sold_currency: sold_currency.first,
|
175
|
+
time: (purchases + sales + fees).map(&:date).last,
|
176
|
+
bought_amount: (purchases + fees).map(&:amount).reduce(&:+),
|
177
|
+
sold_amount: -sales.map(&:amount).reduce(&:+)
|
178
|
+
)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
raise "BitBay API importer error: Couldn't match some history lines: #{matching}"
|
184
|
+
end
|
185
|
+
|
186
|
+
def make_request(method, params = {})
|
187
|
+
(@public_key && @secret_key) or raise "Public and secret API keys must be provided"
|
188
|
+
|
189
|
+
url = URI(BASE_URL)
|
190
|
+
|
191
|
+
params['method'] = method
|
192
|
+
params['moment'] = Time.now.to_i
|
193
|
+
|
194
|
+
param_string = URI.encode_www_form(params)
|
195
|
+
hmac = OpenSSL::HMAC.hexdigest('sha512', @secret_key, param_string)
|
196
|
+
|
197
|
+
Request.post(url) do |request|
|
198
|
+
request.body = param_string
|
199
|
+
request['API-Key'] = @public_key
|
200
|
+
request['API-Hash'] = hmac
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def fetch_info
|
205
|
+
response = make_request('info')
|
206
|
+
|
207
|
+
case response
|
208
|
+
when Net::HTTPSuccess
|
209
|
+
json = JSON.parse(response.body)
|
210
|
+
|
211
|
+
if json['success'] != 1 || json['code'] || json['balances'].nil?
|
212
|
+
raise "BitBay API importer: Invalid response: #{response.body}"
|
213
|
+
end
|
214
|
+
|
215
|
+
json
|
216
|
+
when Net::HTTPBadRequest
|
217
|
+
raise "BitBay API importer: Bad request: #{response}"
|
218
|
+
else
|
219
|
+
raise "BitBay API importer: Bad response: #{response}"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'csv'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
require_relative 'base'
|
6
|
+
require_relative '../currencies'
|
7
|
+
require_relative '../transaction'
|
8
|
+
|
9
|
+
module CoinSync
|
10
|
+
module Importers
|
11
|
+
class Bitcurex < Base
|
12
|
+
register_importer :bitcurex
|
13
|
+
|
14
|
+
class HistoryEntry
|
15
|
+
attr_accessor :lp, :type, :date, :market, :amount, :price, :total, :fee, :fee_currency, :id
|
16
|
+
|
17
|
+
def initialize(line)
|
18
|
+
# TODO: force parsing in Polish timezone
|
19
|
+
@lp = line[0].to_i
|
20
|
+
@type = line[1]
|
21
|
+
@date = Time.parse(line[2])
|
22
|
+
@market = FiatCurrency.new(line[3])
|
23
|
+
@amount = BigDecimal.new(line[4])
|
24
|
+
@price = BigDecimal.new(line[5].split(' ').first)
|
25
|
+
@total = BigDecimal.new(line[6].split(' ').first)
|
26
|
+
@fee = BigDecimal.new(line[7].split(' ').first)
|
27
|
+
@fee_currency = line[7].split(' ').last
|
28
|
+
@id = line[8].to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def read_transaction_list(source)
|
33
|
+
csv = CSV.new(source, col_sep: ',')
|
34
|
+
|
35
|
+
transactions = []
|
36
|
+
bitcoin = CryptoCurrency.new('BTC')
|
37
|
+
|
38
|
+
csv.each do |line|
|
39
|
+
next if line.empty?
|
40
|
+
next if line[0] == 'LP'
|
41
|
+
|
42
|
+
entry = HistoryEntry.new(line)
|
43
|
+
|
44
|
+
if entry.type == 'Kup'
|
45
|
+
transactions << Transaction.new(
|
46
|
+
exchange: 'Bitcurex',
|
47
|
+
bought_currency: bitcoin,
|
48
|
+
sold_currency: entry.market,
|
49
|
+
time: entry.date,
|
50
|
+
bought_amount: entry.amount,
|
51
|
+
sold_amount: entry.total
|
52
|
+
)
|
53
|
+
elsif entry.type == 'Sprzedaj'
|
54
|
+
transactions << Transaction.new(
|
55
|
+
exchange: 'Bitcurex',
|
56
|
+
bought_currency: entry.market,
|
57
|
+
sold_currency: bitcoin,
|
58
|
+
time: entry.date,
|
59
|
+
bought_amount: entry.total,
|
60
|
+
sold_amount: entry.amount
|
61
|
+
)
|
62
|
+
else
|
63
|
+
raise "Bitcurex importer error: unexpected entry type '#{entry.type}'"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
transactions.reverse
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require 'openssl'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
require_relative 'base'
|
8
|
+
require_relative '../balance'
|
9
|
+
require_relative '../currencies'
|
10
|
+
require_relative '../request'
|
11
|
+
|
12
|
+
module CoinSync
|
13
|
+
module Importers
|
14
|
+
class BittrexAPI < Base
|
15
|
+
register_importer :bittrex_api
|
16
|
+
|
17
|
+
BASE_URL = "https://bittrex.com/api/v1.1"
|
18
|
+
|
19
|
+
def initialize(config, params = {})
|
20
|
+
super
|
21
|
+
|
22
|
+
# only "Read Info" permission is required for the key
|
23
|
+
@api_key = params['api_key']
|
24
|
+
@api_secret = params['api_secret']
|
25
|
+
end
|
26
|
+
|
27
|
+
def can_import?(type)
|
28
|
+
@api_key && @api_secret && [:balances].include?(type)
|
29
|
+
end
|
30
|
+
|
31
|
+
def can_build?
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def import_balances
|
36
|
+
response = make_request('/account/getbalances')
|
37
|
+
|
38
|
+
case response
|
39
|
+
when Net::HTTPSuccess
|
40
|
+
json = JSON.parse(response.body)
|
41
|
+
|
42
|
+
if json['success'] != true || !json['result']
|
43
|
+
raise "Bittrex importer: Invalid response: #{response.body}"
|
44
|
+
end
|
45
|
+
|
46
|
+
return json['result'].select { |b|
|
47
|
+
b['Balance'] > 0
|
48
|
+
}.map { |b|
|
49
|
+
Balance.new(
|
50
|
+
CryptoCurrency.new(b['Currency']),
|
51
|
+
available: BigDecimal.new(b['Available'], 0),
|
52
|
+
locked: BigDecimal.new(b['Balance'], 0) - BigDecimal.new(b['Available'], 0)
|
53
|
+
)
|
54
|
+
}
|
55
|
+
when Net::HTTPBadRequest
|
56
|
+
raise "Bittrex importer: Bad request: #{response}"
|
57
|
+
else
|
58
|
+
raise "Bittrex importer: Bad response: #{response}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def make_request(path, params = {})
|
65
|
+
(@api_key && @api_secret) or raise "Public and secret API keys must be provided"
|
66
|
+
|
67
|
+
params['apikey'] = @api_key
|
68
|
+
params['nonce'] = (Time.now.to_f * 1000).to_i
|
69
|
+
|
70
|
+
url = URI(BASE_URL + path)
|
71
|
+
url.query = URI.encode_www_form(params)
|
72
|
+
|
73
|
+
hmac = OpenSSL::HMAC.hexdigest('sha512', @api_secret, url.to_s)
|
74
|
+
|
75
|
+
Request.get(url) do |request|
|
76
|
+
request['apisign'] = hmac
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|