bitopro 0.1.1 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 592f45307f73b0a481ac447c06b61154a1fa302b13522de77b0d2c7439fe9511
4
- data.tar.gz: 7835a701ec02aa883e3a168c7db6690adef5d95d5d7a045180b6e4e9499cc2f0
3
+ metadata.gz: 400df4fabd57efac65d8ce91cf6b8b1f6f5175dfecd142e0639f7134cb83a466
4
+ data.tar.gz: '06871ef22a043496f61aac115ecdd869d2187749864cdd725c44e0a87f2554fb'
5
5
  SHA512:
6
- metadata.gz: 7eaa44e533d2788c03394d7aa725017e3b0396446072b1e30f4a7801a85b2d5bcb2c09056cc47baa90c5d290b5c87d84b3374e648b9618c2900d46a4023a94a5
7
- data.tar.gz: b523c8cb40a295c8efa6d2f9b584206b56402b02cedd3a4d11b149e1fd73c9a7c2bc5e492ac2cba0c978525b34691c5a0a20960d85fcb96911716a9207784738
6
+ metadata.gz: a7a76aaf65109f2a8273c1234189c6fef9ab649e2e70d07bf25058622c4fb06a33d6046a29432a4b42f5bf7ef29a8ac6b916d7cfa50ef950e2d3eaf0355272f2
7
+ data.tar.gz: c2aab8d98acbf5b6ea015ec41596f6902c53dc57d5fbf47f7980a29c96869cef6c0858f5b2251bb8a5e8689b87a9a7fd803d07088df6b3f9b5988cc4ea1f7997
data/lib/bitopro.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  require "rest-client"
2
2
  require "openssl"
3
3
  require "json"
4
+ require "base64"
4
5
 
5
6
  require "bitopro/version"
7
+ require "bitopro/client"
6
8
  require "bitopro/config"
7
- require "bitopro/config/validator"
8
9
 
9
10
  module Bitopro
10
11
  BASE_URL = "https://api.bitopro.com/v2".freeze
@@ -13,7 +14,7 @@ module Bitopro
13
14
  # Configures the Bitopro API.
14
15
  #
15
16
  # @example
16
- # Bitopro.configure do |c|
17
+ # Bitopro.configure do |config|
17
18
  # config.key = 113743
18
19
  # config.secret = 'fd04e13d806a90f96614ad8e529b2822'
19
20
  # end
@@ -24,17 +25,7 @@ module Bitopro
24
25
  def configured?
25
26
  Bitopro::Config.instance.valid?
26
27
  end
27
-
28
-
29
- end
30
-
31
- def self.order_book(currency_pair)
32
- JSON.parse(resource["order-book"][currency_pair].get)
33
28
  end
29
+ end
34
30
 
35
- protected
36
31
 
37
- def self.resource
38
- @resource ||= RestClient::Resource.new(BASE_URL)
39
- end
40
- end
@@ -0,0 +1,15 @@
1
+ module Bitopro
2
+ module Account
3
+ def account_balance
4
+ authenticated_get("/accounts/balance")
5
+ end
6
+
7
+ def order_history
8
+ authenticated_get("/orders/history")
9
+ end
10
+
11
+ def order_list(pair: "", page: 1, active: false)
12
+ authenticated_get("/orders/#{currency_pair}", params: { page: page, active: active })
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ module Bitopro
2
+ module Order
3
+ # Example:
4
+ # client = Bitopro::Client.new
5
+ # client.create_order(pair: "bito_eth", action: "buy", amount: "600", price: "0.000001", type: "limit")
6
+ def create_order(pair: "", action: "", amount: 0, price: 0, type: "limit")
7
+ authenticated_post("/orders/#{pair}", { body: { action: action,
8
+ amount: amount,
9
+ price: price,
10
+ timestamp: timestamp,
11
+ type: type }})
12
+ end
13
+
14
+ # Example:
15
+ # client = Bitopro::Client.new
16
+ # client.cancel_order(pair: "bito_eth", order_id: "3135725012")
17
+ def cancel_order(pair: "", order_id: "")
18
+ authenticated_delete("/orders/#{pair}/#{order_id}")
19
+ end
20
+
21
+ def get_order(pair: "", order_id: "")
22
+ authenticated_get("/orders/#{pair}/#{order_id}")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ module Bitopro
2
+ module Public
3
+ def order_book(currency_pair)
4
+ get("/order-book/#{currency_pair}")
5
+ end
6
+
7
+ def ticker(currency_pair)
8
+ get("/ticker/#{currency_pair}")
9
+ end
10
+
11
+ def recent_trades(currency_pair)
12
+ get("/trades/#{currency_pair}")
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,109 @@
1
+ require_relative './api/account'
2
+ require_relative './api/public'
3
+ require_relative './api/order'
4
+
5
+ module Bitopro
6
+ class Client
7
+ class BitoproSetupError < StandardError; end
8
+
9
+ BASE_URL = "https://api.bitopro.com/v2".freeze
10
+
11
+ include Bitopro::Public
12
+ include Bitopro::Account
13
+ include Bitopro::Order
14
+
15
+ def initialize
16
+ @config = Bitopro::Config.instance
17
+ end
18
+
19
+ private
20
+
21
+ def build_url(url)
22
+ "#{BASE_URL}#{url}"
23
+ end
24
+
25
+ def authenticated_get(url, params = {})
26
+ raise BitoproSetupError, "Must be set configure before call authenticate action" unless Bitopro.configured?
27
+
28
+ complete_url = build_url(url)
29
+
30
+ json_body = { identity: @config.email, nonce: timestamp }.to_json
31
+ payload = build_payload(json_body)
32
+ headers = build_headers(payload)
33
+
34
+ response = RestClient::Request.execute(method: :get,
35
+ url: complete_url,
36
+ headers: headers,
37
+ payload: params,
38
+ timeout: 10)
39
+
40
+ JSON.parse(response.body)
41
+ end
42
+
43
+ def authenticated_post(url, params = {})
44
+ raise BitoproSetupError, "Must be set configure before call authenticate action" unless Bitopro.configured?
45
+
46
+ complete_url = build_url(url)
47
+
48
+ json_body = params[:body].to_json
49
+ payload = build_payload(json_body)
50
+ headers = build_headers(payload)
51
+
52
+ response = RestClient::Request.execute(method: :post,
53
+ url: complete_url,
54
+ headers: headers,
55
+ payload: json_body,
56
+ timeout: 10)
57
+
58
+ JSON.parse(response.body)
59
+ end
60
+
61
+ def authenticated_delete(url, params = {})
62
+ raise BitoproSetupError, "Must be set configure before call authenticate action" unless Bitopro.configured?
63
+
64
+ complete_url = build_url(url)
65
+
66
+ json_body = { identity: @config.email, nonce: timestamp }.to_json
67
+ payload = build_payload(json_body)
68
+ headers = build_headers(payload)
69
+
70
+ response = RestClient::Request.execute(method: :delete,
71
+ url: complete_url,
72
+ headers: headers,
73
+ payload: params,
74
+ timeout: 10)
75
+
76
+ JSON.parse(response.body)
77
+ end
78
+
79
+ def get(url, params = {})
80
+ complete_url = build_url(url)
81
+ response = RestClient::Request.execute(method: :get,
82
+ url: complete_url,
83
+ payload: params,
84
+ timeout: 10)
85
+
86
+ JSON.parse(response.body)
87
+ end
88
+
89
+ def build_payload(json_body)
90
+ Base64.strict_encode64(json_body)
91
+ end
92
+
93
+ def timestamp
94
+ (Time.now.to_f * 1000).floor
95
+ end
96
+
97
+ def signature(payload)
98
+ OpenSSL::HMAC.hexdigest("SHA384", @config.secret, payload)
99
+ end
100
+
101
+ def build_headers(payload)
102
+ {
103
+ "X-BITOPRO-APIKEY": @config.key,
104
+ "X-BITOPRO-PAYLOAD": payload,
105
+ "X-BITOPRO-SIGNATURE": signature(payload)
106
+ }
107
+ end
108
+ end
109
+ end
@@ -12,14 +12,16 @@ module Bitopro
12
12
 
13
13
  attr_accessor :key
14
14
  attr_accessor :secret
15
+ attr_accessor :email
15
16
 
16
17
  def initialize(user_config = {})
17
18
  self.key = user_config[:key]
18
19
  self.secret = user_config[:secret]
20
+ self.email = user_config[:email]
19
21
  end
20
22
 
21
23
  def valid?
22
- Validator.validate(self)
24
+ !self.key.empty? && !self.secret.empty? && !self.email.empty? rescue false
23
25
  end
24
26
  end
25
27
  end
@@ -1,3 +1,3 @@
1
1
  module Bitopro
2
- VERSION = "0.1.1".freeze
2
+ VERSION = "1.0.0".freeze
3
3
  end
@@ -13,12 +13,39 @@ RSpec.describe Bitopro do
13
13
  it "makes Bitopro configured" do
14
14
  expect(described_class).not_to be_configured
15
15
 
16
- described_class.configure do |c|
17
- c.key = 1
18
- c.secret = '2'
16
+ described_class.configure do |config|
17
+ config.email = "bitopro@ruby.rspec"
18
+ config.key = "1"
19
+ config.secret = "2"
19
20
  end
20
21
 
21
22
  expect(described_class).to be_configured
22
23
  end
23
24
  end
25
+
26
+ describe "#order_book" do
27
+ let(:currency_pair) { "btc_twd" }
28
+
29
+ subject { Bitopro::Client.new.order_book(currency_pair) }
30
+
31
+ context "when currency pair is empty" do
32
+
33
+ end
34
+
35
+ context "when api responds with error" do
36
+
37
+ end
38
+
39
+ context "when api succeeds" do
40
+ let!(:request_stub) do
41
+ stub_request(:get, "#{Bitopro::BASE_URL}/order-book/#{currency_pair}")
42
+ .to_return(status: 200, body: json_fixture('order-book'))
43
+ end
44
+
45
+ it "return parsed json order book" do
46
+ subject
47
+ expect(request_stub).to have_been_requested
48
+ end
49
+ end
50
+ end
24
51
  end
@@ -0,0 +1,246 @@
1
+ {
2
+ "asks": [
3
+ {
4
+ "amount": "1",
5
+ "count": 1,
6
+ "price": "160990",
7
+ "total": "1"
8
+ },
9
+ {
10
+ "amount": "0.141351",
11
+ "count": 1,
12
+ "price": "161000",
13
+ "total": "1.141351"
14
+ },
15
+ {
16
+ "amount": "0.011",
17
+ "count": 1,
18
+ "price": "161899",
19
+ "total": "1.152351"
20
+ },
21
+ {
22
+ "amount": "0.011",
23
+ "count": 1,
24
+ "price": "161929",
25
+ "total": "1.163351"
26
+ },
27
+ {
28
+ "amount": "0.011",
29
+ "count": 1,
30
+ "price": "161979",
31
+ "total": "1.174351"
32
+ },
33
+ {
34
+ "amount": "0.00088542",
35
+ "count": 1,
36
+ "price": "161986",
37
+ "total": "1.17523642"
38
+ },
39
+ {
40
+ "amount": "0.038",
41
+ "count": 2,
42
+ "price": "161999",
43
+ "total": "1.21323642"
44
+ },
45
+ {
46
+ "amount": "0.011",
47
+ "count": 1,
48
+ "price": "162489",
49
+ "total": "1.22423642"
50
+ },
51
+ {
52
+ "amount": "0.1",
53
+ "count": 1,
54
+ "price": "162490",
55
+ "total": "1.32423642"
56
+ },
57
+ {
58
+ "amount": "0.011",
59
+ "count": 1,
60
+ "price": "162498",
61
+ "total": "1.33523642"
62
+ },
63
+ {
64
+ "amount": "0.099",
65
+ "count": 1,
66
+ "price": "162499",
67
+ "total": "1.43423642"
68
+ },
69
+ {
70
+ "amount": "0.02744999",
71
+ "count": 1,
72
+ "price": "162500",
73
+ "total": "1.46168641"
74
+ },
75
+ {
76
+ "amount": "0.099",
77
+ "count": 1,
78
+ "price": "162589",
79
+ "total": "1.56068641"
80
+ },
81
+ {
82
+ "amount": "0.55968182",
83
+ "count": 5,
84
+ "price": "162599",
85
+ "total": "2.12036823"
86
+ },
87
+ {
88
+ "amount": "0.099",
89
+ "count": 1,
90
+ "price": "162799",
91
+ "total": "2.21936823"
92
+ },
93
+ {
94
+ "amount": "1.2213",
95
+ "count": 2,
96
+ "price": "162800",
97
+ "total": "3.44066823"
98
+ },
99
+ {
100
+ "amount": "0.11",
101
+ "count": 1,
102
+ "price": "162989",
103
+ "total": "3.55066823"
104
+ },
105
+ {
106
+ "amount": "2.06791042",
107
+ "count": 3,
108
+ "price": "162999",
109
+ "total": "5.61857865"
110
+ },
111
+ {
112
+ "amount": "1.4",
113
+ "count": 2,
114
+ "price": "163000",
115
+ "total": "7.01857865"
116
+ },
117
+ {
118
+ "amount": "0.1",
119
+ "count": 1,
120
+ "price": "163100",
121
+ "total": "7.11857865"
122
+ }
123
+ ],
124
+ "bids": [
125
+ {
126
+ "amount": "0.03324",
127
+ "count": 1,
128
+ "price": "160800",
129
+ "total": "0.03324"
130
+ },
131
+ {
132
+ "amount": "0.74529743",
133
+ "count": 1,
134
+ "price": "160799",
135
+ "total": "0.77853743"
136
+ },
137
+ {
138
+ "amount": "0.01400139",
139
+ "count": 1,
140
+ "price": "160700",
141
+ "total": "0.79253882"
142
+ },
143
+ {
144
+ "amount": "0.04",
145
+ "count": 1,
146
+ "price": "160300",
147
+ "total": "0.83253882"
148
+ },
149
+ {
150
+ "amount": "0.13875606",
151
+ "count": 1,
152
+ "price": "160100",
153
+ "total": "0.97129488"
154
+ },
155
+ {
156
+ "amount": "0.1",
157
+ "count": 1,
158
+ "price": "160001",
159
+ "total": "1.07129488"
160
+ },
161
+ {
162
+ "amount": "0.23325526",
163
+ "count": 2,
164
+ "price": "160000",
165
+ "total": "1.30455014"
166
+ },
167
+ {
168
+ "amount": "0.07854057",
169
+ "count": 1,
170
+ "price": "159022",
171
+ "total": "1.38309071"
172
+ },
173
+ {
174
+ "amount": "0.5",
175
+ "count": 1,
176
+ "price": "159000",
177
+ "total": "1.88309071"
178
+ },
179
+ {
180
+ "amount": "0.02",
181
+ "count": 1,
182
+ "price": "158200",
183
+ "total": "1.90309071"
184
+ },
185
+ {
186
+ "amount": "0.1",
187
+ "count": 1,
188
+ "price": "158100",
189
+ "total": "2.00309071"
190
+ },
191
+ {
192
+ "amount": "0.23093353",
193
+ "count": 3,
194
+ "price": "158000",
195
+ "total": "2.23402424"
196
+ },
197
+ {
198
+ "amount": "0.67424151",
199
+ "count": 5,
200
+ "price": "157000",
201
+ "total": "2.90826575"
202
+ },
203
+ {
204
+ "amount": "0.1",
205
+ "count": 1,
206
+ "price": "156900",
207
+ "total": "3.00826575"
208
+ },
209
+ {
210
+ "amount": "0.00269",
211
+ "count": 1,
212
+ "price": "156850",
213
+ "total": "3.01095575"
214
+ },
215
+ {
216
+ "amount": "0.5666454",
217
+ "count": 2,
218
+ "price": "156800",
219
+ "total": "3.57760115"
220
+ },
221
+ {
222
+ "amount": "0.03189647",
223
+ "count": 1,
224
+ "price": "156765",
225
+ "total": "3.60949762"
226
+ },
227
+ {
228
+ "amount": "0.1",
229
+ "count": 1,
230
+ "price": "156650",
231
+ "total": "3.70949762"
232
+ },
233
+ {
234
+ "amount": "0.58114303",
235
+ "count": 1,
236
+ "price": "156600",
237
+ "total": "4.29064065"
238
+ },
239
+ {
240
+ "amount": "0.05",
241
+ "count": 1,
242
+ "price": "156500",
243
+ "total": "4.34064065"
244
+ }
245
+ ]
246
+ }
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "rspec"
2
+ require "rest-client"
2
3
  require "bundler/setup"
3
4
  require 'webmock/rspec'
4
5
  require 'pry'
@@ -7,7 +8,6 @@ require 'bitopro'
7
8
  Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each { |file| require file }
8
9
 
9
10
  WebMock.disable_net_connect!
10
-
11
11
  RSpec.configure do |config|
12
12
  # Enable flags like --only-failures and --next-failure
13
13
  config.example_status_persistence_file_path = ".rspec_status"
@@ -15,6 +15,8 @@ RSpec.configure do |config|
15
15
  # Disable RSpec exposing methods globally on `Module` and `main`
16
16
  config.disable_monkey_patching!
17
17
 
18
+ config.include RSpecHelpers
19
+
18
20
  config.expect_with :rspec do |c|
19
21
  c.syntax = :expect
20
22
  end
@@ -0,0 +1,13 @@
1
+ module RSpecHelpers
2
+ def file_fixture(path)
3
+ File.read(File.join('spec', 'fixtures', path))
4
+ end
5
+
6
+ def is_expected_block
7
+ expect { subject }
8
+ end
9
+
10
+ def json_fixture(fixture_name)
11
+ file_fixture("#{fixture_name}.json")
12
+ end
13
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitopro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - niclin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-17 00:00:00.000000000 Z
11
+ date: 2019-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rest-client
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  description: Ruby wrapper for the Bitopro API.
84
98
  email:
85
99
  - bboyceo@hotmail.com
@@ -88,11 +102,16 @@ extensions: []
88
102
  extra_rdoc_files: []
89
103
  files:
90
104
  - lib/bitopro.rb
105
+ - lib/bitopro/api/account.rb
106
+ - lib/bitopro/api/order.rb
107
+ - lib/bitopro/api/public.rb
108
+ - lib/bitopro/client.rb
91
109
  - lib/bitopro/config.rb
92
- - lib/bitopro/config/validator.rb
93
110
  - lib/bitopro/version.rb
94
111
  - spec/bitopro/api_spec.rb
112
+ - spec/fixtures/order-book.json
95
113
  - spec/spec_helper.rb
114
+ - spec/support/rspec_helpers.rb
96
115
  homepage: https://github.com/niclin/bitopro
97
116
  licenses:
98
117
  - MIT
@@ -118,7 +137,9 @@ rubyforge_project:
118
137
  rubygems_version: 2.7.8
119
138
  signing_key:
120
139
  specification_version: 4
121
- summary: bitopro-0.1.1
140
+ summary: bitopro-1.0.0
122
141
  test_files:
123
142
  - spec/spec_helper.rb
124
143
  - spec/bitopro/api_spec.rb
144
+ - spec/support/rspec_helpers.rb
145
+ - spec/fixtures/order-book.json
@@ -1,35 +0,0 @@
1
- module Bitopro
2
- class Config
3
- class Validator
4
- class << self
5
- Error = Class.new(StandardError)
6
-
7
- def validate(config)
8
- errors = []
9
-
10
- unless valid_key?(config)
11
- errors << 'key is required'
12
- end
13
- unless valid_secret?(config)
14
- errors << 'secret is required'
15
- end
16
-
17
- errors.empty?
18
- end
19
-
20
- private
21
-
22
- def valid_key?(config)
23
- return true if config.key.to_i > 0
24
- false
25
- end
26
-
27
- def valid_secret?(config)
28
- return false unless config.secret.is_a?(String)
29
- return false if config.secret.empty?
30
- true
31
- end
32
- end
33
- end
34
- end
35
- end