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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +302 -0
- data/Rakefile +10 -0
- data/buttercoin.gemspec +26 -0
- data/lib/buttercoin.rb +78 -0
- data/lib/buttercoin/cert/ca-cert.crt +629 -0
- data/lib/buttercoin/client.rb +135 -0
- data/lib/buttercoin/client/account_query_methods.rb +32 -0
- data/lib/buttercoin/client/order_methods.rb +66 -0
- data/lib/buttercoin/client/transaction_methods.rb +89 -0
- data/lib/buttercoin/client/unauth_methods.rb +23 -0
- data/lib/buttercoin/version.rb +3 -0
- data/spec/buttercoin/client_spec.rb +105 -0
- data/spec/buttercoin_spec.rb +79 -0
- data/spec/spec_helper.rb +3 -0
- metadata +146 -0
@@ -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,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
|