buttercoin 0.0.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,135 @@
1
+ require 'httparty'
2
+ require 'hashie'
3
+ require 'buttercoin/version'
4
+ require 'buttercoin/client/unauth_methods'
5
+ require 'buttercoin/client/account_query_methods'
6
+ require 'buttercoin/client/order_methods'
7
+ require 'buttercoin/client/transaction_methods'
8
+ #require 'openssl'
9
+ require 'base64'
10
+
11
+ module Buttercoin
12
+ class Client
13
+ include HTTParty
14
+ include Buttercoin::Client::UnauthMethods
15
+ include Buttercoin::Client::AccountQueryMethods
16
+ include Buttercoin::Client::OrderMethods
17
+ include Buttercoin::Client::TransactionMethods
18
+
19
+ attr_accessor :public_key, :secret_key, :mode
20
+
21
+ CONFIG = {
22
+ :mode => "production"
23
+ }
24
+
25
+ PRODUCTION_URI = 'https://api.buttercoin.com/v1'
26
+ SANDBOX_URI = 'https://api.qa.dcxft.com/v1'
27
+
28
+ CA_CERT = 'cert/ca-cert.crt'
29
+
30
+ def initialize(*args)
31
+ options = args[0]
32
+ unless options.is_a?(Hash)
33
+ # deprecated, pass a hash of options instead
34
+ options = {
35
+ :public_key => args[0],
36
+ :secret_key => args[1],
37
+ :mode => args[2]
38
+ }
39
+ end
40
+
41
+ self.public_key, self.secret_key = options.values_at(:public_key, :secret_key)
42
+ self.mode = options[:mode] || CONFIG[:mode]
43
+ self.class.base_uri (options[:mode] == 'production') ? PRODUCTION_URI : SANDBOX_URI
44
+ end
45
+
46
+ # Wrappers for the main HTTP verbs
47
+
48
+ def get(path, timestamp=nil, options={}, authenticate=true)
49
+ http_request :get, path, timestamp, options, authenticate
50
+ end
51
+
52
+ def post(path, timestamp=nil, options={}, authenticate=true)
53
+ http_request :post, path, timestamp, options, authenticate
54
+ end
55
+
56
+ def delete(path, timestamp=nil, options={}, authenticate=true)
57
+ http_request :delete, path, timestamp, options, authenticate
58
+ end
59
+
60
+ def http_request(verb, path, timestamp=nil, options={}, authenticate=true)
61
+ request_options = {}
62
+ if (authenticate)
63
+ timestamp ||= ((Time.now.to_f * 1e3).round).to_i
64
+ message = build_message(verb, path, timestamp, options)
65
+ signature = get_signature(message)
66
+ request_options = {body: options.to_json} if [:post].include? verb
67
+ request_options[:headers] = get_headers(signature, timestamp)
68
+ end
69
+ set_cert()
70
+ r = self.class.send(verb, path, request_options)
71
+ process_response(r.code, r.body, r.headers)
72
+ end
73
+
74
+ private
75
+
76
+ def set_cert
77
+ path = File.expand_path( CA_CERT, __FILE__)
78
+ self.class.ssl_ca_file path
79
+ end
80
+
81
+ def build_message(verb, path, timestamp, options)
82
+ if [:get, :delete].include? verb
83
+ path = "#{path}?#{URI.encode_www_form(options)}" if !options.empty?
84
+ message = timestamp.to_s + self.class.base_uri + path
85
+ else
86
+ message = timestamp.to_s + self.class.base_uri + path + options.to_json
87
+ end
88
+ return message
89
+ end
90
+
91
+ def get_signature(message)
92
+ message = Base64.encode64(message).gsub(/\n/, "")
93
+ return Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), self.secret_key, message))
94
+ end
95
+
96
+ def get_headers(signature, timestamp)
97
+ return {
98
+ 'X-Buttercoin-Access-Key' => self.public_key,
99
+ 'X-Buttercoin-Signature' => signature,
100
+ 'X-Buttercoin-Date' => timestamp.to_s,
101
+ "Content-Type" => "application/json"
102
+ }
103
+ end
104
+
105
+ def process_response(status_code, response_body, response_headers)
106
+ case status_code.to_i
107
+ when 200
108
+ return Hashie::Mash.new(JSON.parse(response_body))
109
+ when 201
110
+ return Hashie::Mash.new({ "status" => status_code, "message" => 'This operation requires email confirmation' })
111
+ when 202
112
+ return response_headers["location"]
113
+ when 204
114
+ return Hashie::Mash.new({ "status" => status_code, "message" => 'This operation has completed successfully' })
115
+ when 400
116
+ mash = Hashie::Mash.new(JSON.parse(response_body))
117
+ raise BadRequestError.new(mash.errors.first.message)
118
+ when 401
119
+ mash = Hashie::Mash.new(JSON.parse(response_body))
120
+ raise AuthenticationError.new(mash.errors.first.message)
121
+ when 404
122
+ mash = Hashie::Mash.new(JSON.parse(response_body))
123
+ raise NotFoundError.new(mash.errors.first.message)
124
+ else
125
+ begin
126
+ mash = Hashie::Mash.new(JSON.parse(response_body))
127
+ raise HttpError.new(mash.errors.first.message)
128
+ rescue
129
+ raise HttpError.new(response_body)
130
+ end
131
+ end
132
+ end
133
+
134
+ end
135
+ end
@@ -0,0 +1,32 @@
1
+ module Buttercoin
2
+ class Client
3
+ module AccountQueryMethods
4
+
5
+ # Retrieve the ticker
6
+ #
7
+ # @param timestamp integer (optional)
8
+
9
+ def get_key(timestamp=nil)
10
+ get '/key', timestamp
11
+ end
12
+
13
+ # Retrieve the account balanaces for all currencies
14
+ #
15
+ # @param timestamp integer (optional)
16
+
17
+ def get_balances(timestamp=nil)
18
+ get '/account/balances', timestamp
19
+ end
20
+
21
+ # Retrieve the bitcoin deposit address
22
+ #
23
+ # @param timestamp integer (optional)
24
+
25
+ def get_deposit_address(timestamp=nil)
26
+ mash = get '/account/depositAddress', timestamp
27
+ mash.address
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,66 @@
1
+ module Buttercoin
2
+ class Client
3
+ module OrderMethods
4
+
5
+ # Retrieve the order by id
6
+ #
7
+ # @orderId string
8
+ # @param timestamp integer (optional)
9
+ #
10
+ # @return Hashie object containing order info
11
+
12
+ def get_order_by_id(orderId, timestamp=nil)
13
+ get '/orders/'+orderId, timestamp
14
+ end
15
+
16
+ # Retrieve the order by url
17
+ #
18
+ # @param url full url of the order request
19
+ # @param timestamp integer (optional)
20
+ #
21
+ # @return Hashie object containing order info
22
+
23
+ def get_order_by_url(url, timestamp=nil)
24
+ index = url.rindex('/orders')
25
+ raise Error.new('Url not correctly formatted for orders') if index.nil?
26
+ path = url[index..-1]
27
+ get path, timestamp
28
+ end
29
+
30
+ # Get list of orders by search criteria
31
+ #
32
+ # @param timestamp integer (optional)
33
+ # @param options Hash (optional) criteria to filter list
34
+ #
35
+ # @return Hashie object containing list of orders
36
+
37
+ def get_orders(options={}, timestamp=nil)
38
+ mash = get '/orders', timestamp, options
39
+ mash.results
40
+ end
41
+
42
+ # Create new order with the given params
43
+ #
44
+ # @param timestamp integer (optional)
45
+ # @param options Hash (required) order params
46
+ #
47
+ # @return string containing response location header url
48
+
49
+ def create_order(options, timestamp=nil)
50
+ post '/orders', timestamp, options
51
+ end
52
+
53
+ # Cancel order by id
54
+ #
55
+ # @orderId string
56
+ # @param timestamp integer (optional)
57
+ #
58
+ # @return Hashie object containing status and success message
59
+
60
+ def cancel_order(orderId, timestamp=nil)
61
+ delete '/orders/'+orderId, timestamp
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,89 @@
1
+ module Buttercoin
2
+ class Client
3
+ module TransactionMethods
4
+
5
+ # Retrieve the transaction by id
6
+ #
7
+ # @transactionId string
8
+ # @param timestamp integer (optional)
9
+ #
10
+ # @return Hashie object containing transaction info
11
+
12
+ def get_transaction_by_id(transactionId, timestamp=nil)
13
+ get '/transactions/'+transactionId, timestamp
14
+ end
15
+
16
+ # Retrieve the transaction by url
17
+ #
18
+ # @param url full url of the transaction request
19
+ # @param timestamp integer (optional)
20
+ #
21
+ # @return Hashie object containing transaction info
22
+
23
+ def get_transaction_by_url(url, timestamp=nil)
24
+ index = url.rindex('/transactions')
25
+ raise Error.new('Url not correctly formatted for transactions') if index.nil?
26
+ path = url[index..-1]
27
+ get path, timestamp
28
+ end
29
+
30
+ # Get list of transactions by search criteria
31
+ #
32
+ # @param timestamp integer (optional)
33
+ # @param options Hash (optional) criteria to filter list
34
+ #
35
+ # @return Hashie object containing list of transactions
36
+
37
+ def get_transactions(options={}, timestamp=nil)
38
+ mash = get '/transactions', timestamp, options
39
+ mash.results
40
+ end
41
+
42
+ # Create new deposit with the given params
43
+ #
44
+ # @param timestamp integer (optional)
45
+ # @param options Hash (required) transaction params
46
+ #
47
+ # @return string containing response location header url
48
+
49
+ def create_deposit(options, timestamp=nil)
50
+ post '/transactions/deposit', timestamp, options
51
+ end
52
+
53
+ # Create new withdrawal with the given params
54
+ #
55
+ # @param timestamp integer (optional)
56
+ # @param options Hash (required) transaction params
57
+ #
58
+ # @return string containing response location header url
59
+
60
+ def create_withdrawal(options, timestamp=nil)
61
+ post '/transactions/withdraw', timestamp, options
62
+ end
63
+
64
+ # Create new bitcoin withdrawal with the given params
65
+ #
66
+ # @param timestamp integer (optional)
67
+ # @param options Hash (required) transaction params
68
+ #
69
+ # @return string containing response location header url
70
+
71
+ def send_bitcoin(options, timestamp=nil)
72
+ post '/transactions/send', timestamp, options
73
+ end
74
+
75
+ # Cancel transaction by id
76
+ #
77
+ # @transactionId string
78
+ # @param timestamp integer (optional)
79
+ #
80
+ # @return Hashie object containing status and success message
81
+
82
+ def cancel_transaction(transactionId, timestamp=nil)
83
+ delete '/transactions/'+transactionId, timestamp
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+
@@ -0,0 +1,23 @@
1
+ module Buttercoin
2
+ class Client
3
+ module UnauthMethods
4
+
5
+ # Retrieve the ticker
6
+ #
7
+ # @return Hashie object containing market price info
8
+
9
+ def get_ticker
10
+ get '/ticker', nil, {}, false
11
+ end
12
+
13
+ # Retrieve the orderbook
14
+ #
15
+ # @return Hashie object containing order book info
16
+
17
+ def get_order_book
18
+ get '/orderbook', nil, {}, false
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module Buttercoin
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+ require 'fakeweb'
3
+ require 'buttercoin'
4
+
5
+ describe Buttercoin::Client do
6
+
7
+ let(:public_key) { 'public key' }
8
+ let(:secret_key) { 'secret key' }
9
+ let(:mode) { 'sandbox' }
10
+ let(:client) { Buttercoin::Client.new(:public_key => public_key, :secret_key => secret_key) }
11
+
12
+
13
+ before :all do
14
+ FakeWeb.allow_net_connect = false
15
+ end
16
+
17
+ describe "Initialize client" do
18
+ it "should initialize with the given options" do
19
+ client = Buttercoin::Client.new(
20
+ :public_key => public_key,
21
+ :secret_key => secret_key,
22
+ :mode => mode
23
+ )
24
+ expect(client.public_key).to eq('public key')
25
+ expect(client.secret_key).to eq('secret key')
26
+ expect(client.mode).to eq('sandbox')
27
+ end
28
+
29
+ it "should set the base_uri to the sandbox" do
30
+ expect(Buttercoin::Client.new.class.base_uri).to eq(Buttercoin::Client::SANDBOX_URI)
31
+ end
32
+ end
33
+
34
+ describe "Sign the request" do
35
+ let(:build_message) { client.method(:build_message) }
36
+ let(:get_signature) { client.method(:get_signature) }
37
+ let(:get_headers) { client.method(:get_headers) }
38
+ let(:timestamp) { 1444444444444 }
39
+ let(:path) { '/orders' }
40
+ let(:options) do { :status => "opened" } end
41
+ let(:message) { "1444444444444https://api.qa.dcxft.com/v1/orders?status=opened" }
42
+ let(:signature) { "0OBOwbOX3npNPyI6TLq+PN2jpW1NhLn+St0FOpcKvMc=\n" }
43
+
44
+ it "should build proper get request message for signing" do
45
+ expect(build_message.call(:get, path, timestamp, options)).to eq("1444444444444https://api.qa.dcxft.com/v1/orders?status=opened")
46
+ end
47
+
48
+ it "should build proper get request message for signing" do
49
+ expect(build_message.call(:post, path, timestamp, options)).to eq("1444444444444https://api.qa.dcxft.com/v1/orders{\"status\":\"opened\"}")
50
+ end
51
+
52
+ it "should properly sign a message with the given key" do
53
+ expect(get_signature.call(message)).to eq (signature)
54
+ end
55
+
56
+ it "should properly build the request headers" do
57
+ headers = {
58
+ 'X-Buttercoin-Access-Key' => public_key,
59
+ 'X-Buttercoin-Signature' => signature,
60
+ 'X-Buttercoin-Date' => timestamp.to_s,
61
+ "Content-Type" => "application/json"
62
+ }
63
+ expect(get_headers.call(signature, timestamp)).to eq (headers)
64
+ end
65
+ end
66
+
67
+ describe "process_response" do
68
+ let(:body) { "{ \"text\": \"stuff\" }" }
69
+ let(:error_body) { "{ \"errors\": [ {\"message\": \"stuff\"} ] }" }
70
+ let(:headers) { JSON.parse("{ \"location\": \"http://fake.api.buttercoin.com\" }") }
71
+ let (:process_response) { client.method(:process_response) }
72
+
73
+ it "should return a Hashie::Mash object for a 200" do
74
+ expect(process_response.call(200, body, headers).text).to eq "stuff"
75
+ end
76
+
77
+ it "should return a success message in a Hashie::Mash object for a 201" do
78
+ expect(process_response.call(201, body, headers).message).to eq "This operation requires email confirmation"
79
+ end
80
+
81
+ it "should return a location header string for a 202" do
82
+ expect(process_response.call(202, body, headers)).to eq "http://fake.api.buttercoin.com"
83
+ end
84
+
85
+ it "should return a Hashie::Mash object for a 204" do
86
+ expect(process_response.call(204, body, headers).message).to eq "This operation has completed successfully"
87
+ end
88
+
89
+ it "should raise a BadRequestError for a 400" do
90
+ expect{process_response.call(400, error_body, headers)}.to raise_error(Buttercoin::BadRequestError, "Buttercoin Exception: stuff")
91
+ end
92
+
93
+ it "should raise a BadRequestError for a 401" do
94
+ expect{process_response.call(401, error_body, headers)}.to raise_error(Buttercoin::AuthenticationError, "Buttercoin Exception: stuff")
95
+ end
96
+
97
+ it "should raise a BadRequestError for a 404" do
98
+ expect{process_response.call(404, error_body, headers)}.to raise_error(Buttercoin::NotFoundError, "Buttercoin Exception: stuff")
99
+ end
100
+
101
+ it "should raise a BadRequestError for a 429" do
102
+ expect{process_response.call(429, error_body, headers)}.to raise_error(Buttercoin::HttpError, "Buttercoin Exception: stuff")
103
+ end
104
+ end
105
+ end