coinbase 0.0.1 → 2.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.
Potentially problematic release.
This version of coinbase might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.gitignore +17 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +330 -0
- data/Rakefile +6 -0
- data/coinbase.gemspec +33 -0
- data/lib/coinbase/ca-coinbase.crt +629 -0
- data/lib/coinbase/client.rb +290 -0
- data/lib/coinbase/oauth_client.rb +77 -0
- data/lib/coinbase/version.rb +3 -0
- data/lib/coinbase.rb +5 -18
- data/spec/client_spec.rb +358 -0
- data/spec/oauth_client_spec.rb +81 -0
- data/supported_currencies.json +163 -0
- metadata +71 -90
- data/lib/coinbase/address.rb +0 -127
- data/lib/coinbase/asset.rb +0 -20
- data/lib/coinbase/balance_map.rb +0 -48
- data/lib/coinbase/constants.rb +0 -38
- data/lib/coinbase/network.rb +0 -55
- data/lib/coinbase/transfer.rb +0 -153
- data/lib/coinbase/wallet.rb +0 -160
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
require 'httparty'
|
|
2
|
+
require 'multi_json'
|
|
3
|
+
require 'hashie'
|
|
4
|
+
require 'money'
|
|
5
|
+
require 'monetize'
|
|
6
|
+
require 'time'
|
|
7
|
+
require 'securerandom'
|
|
8
|
+
|
|
9
|
+
module Coinbase
|
|
10
|
+
class Client
|
|
11
|
+
include HTTParty
|
|
12
|
+
|
|
13
|
+
BASE_URI = 'https://coinbase.com/api/v1'
|
|
14
|
+
|
|
15
|
+
def initialize(api_key, api_secret, options={})
|
|
16
|
+
@api_key = api_key
|
|
17
|
+
@api_secret = api_secret
|
|
18
|
+
|
|
19
|
+
# defaults
|
|
20
|
+
options[:base_uri] ||= BASE_URI
|
|
21
|
+
@base_uri = options[:base_uri]
|
|
22
|
+
options[:format] ||= :json
|
|
23
|
+
options.each do |k,v|
|
|
24
|
+
self.class.send k, v
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Account
|
|
29
|
+
|
|
30
|
+
def balance options={}
|
|
31
|
+
h = get '/account/balance', options
|
|
32
|
+
h['amount'].to_money(h['currency'])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def accounts options={}
|
|
36
|
+
accts = get '/accounts', options
|
|
37
|
+
accts = convert_money_objects(accts)
|
|
38
|
+
accts
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def receive_address options={}
|
|
42
|
+
get '/account/receive_address', options
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def generate_receive_address options={}
|
|
46
|
+
post '/account/generate_receive_address', options
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Buttons
|
|
50
|
+
|
|
51
|
+
def create_button name, price, description=nil, custom=nil, options={}
|
|
52
|
+
options[:button] ||= {}
|
|
53
|
+
options[:button][:name] ||= name
|
|
54
|
+
price = price.to_money("BTC") unless price.is_a?(Money)
|
|
55
|
+
options[:button][:price_string] ||= price.to_s
|
|
56
|
+
options[:button][:price_currency_iso] ||= price.currency.iso_code
|
|
57
|
+
options[:button][:description] ||= description
|
|
58
|
+
options[:button][:custom] ||= custom
|
|
59
|
+
r = post '/buttons', options
|
|
60
|
+
if r.success?
|
|
61
|
+
r.embed_html = case options[:button_mode]
|
|
62
|
+
when 'page'
|
|
63
|
+
%[<a href="https://coinbase.com/checkouts/#{r.button.code}" target="_blank"><img alt="#{r.button.text}" src="https://coinbase.com/assets/buttons/#{r.button.style}.png"></a>]
|
|
64
|
+
when 'iframe'
|
|
65
|
+
%[<iframe src="https://coinbase.com/inline_payments/#{r.button.code}" style="width:500px;height:160px;border:none;box-shadow:0 1px 3px rgba(0,0,0,0.25);overflow:hidden;" scrolling="no" allowtransparency="true" frameborder="0"></iframe>]
|
|
66
|
+
else
|
|
67
|
+
%[<div class="coinbase-button" data-code="#{r.button.code}"></div><script src="https://coinbase.com/assets/button.js" type="text/javascript"></script>]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
r
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def create_order_for_button button_id
|
|
74
|
+
post "/buttons/#{button_id}/create_order"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Transactions
|
|
78
|
+
|
|
79
|
+
def transactions page=1, options ={}
|
|
80
|
+
r = get '/transactions', {page: page}.merge(options)
|
|
81
|
+
r.transactions ||= []
|
|
82
|
+
convert_money_objects(r.transactions)
|
|
83
|
+
r
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def transaction transaction_id
|
|
87
|
+
r = get "/transactions/#{transaction_id}"
|
|
88
|
+
convert_money_objects(r.transaction)
|
|
89
|
+
r
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def send_money to, amount, notes=nil, options={}
|
|
93
|
+
options[:transaction] ||= {}
|
|
94
|
+
options[:transaction][:to] ||= to
|
|
95
|
+
amount = amount.to_money("BTC") unless amount.is_a?(Money)
|
|
96
|
+
options[:transaction][:amount_string] ||= amount.to_s
|
|
97
|
+
options[:transaction][:amount_currency_iso] ||= amount.currency.iso_code
|
|
98
|
+
options[:transaction][:notes] ||= notes
|
|
99
|
+
r = post '/transactions/send_money', options
|
|
100
|
+
if amt = r.transaction.amount
|
|
101
|
+
r.transaction.amount = amt.amount.to_money(amt.currency)
|
|
102
|
+
end
|
|
103
|
+
r
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def request_money from, amount, notes=nil, options={}
|
|
107
|
+
options[:transaction] ||= {}
|
|
108
|
+
options[:transaction][:from] ||= from
|
|
109
|
+
amount = amount.to_money("BTC") unless amount.is_a?(Money)
|
|
110
|
+
options[:transaction][:amount_string] ||= amount.to_s
|
|
111
|
+
options[:transaction][:amount_currency_iso] ||= amount.currency.iso_code
|
|
112
|
+
options[:transaction][:notes] ||= notes
|
|
113
|
+
r = post '/transactions/request_money', options
|
|
114
|
+
if amt = r.transaction.amount
|
|
115
|
+
r.transaction.amount = amt.amount.to_money(amt.currency)
|
|
116
|
+
end
|
|
117
|
+
r
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def resend_request transaction_id
|
|
121
|
+
put "/transactions/#{transaction_id}/resend_request"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def cancel_request transaction_id
|
|
125
|
+
delete "/transactions/#{transaction_id}/cancel_request"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def complete_request transaction_id
|
|
129
|
+
put "/transactions/#{transaction_id}/complete_request"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Users
|
|
133
|
+
|
|
134
|
+
def create_user email, password=nil, client_id=nil, scopes=nil
|
|
135
|
+
password ||= SecureRandom.urlsafe_base64(12)
|
|
136
|
+
options = {user: {email: email, password: password}}
|
|
137
|
+
if client_id
|
|
138
|
+
options[:client_id] = client_id
|
|
139
|
+
raise Error.new("Invalid scopes parameter; must be an array") if !scopes.is_a?(Array)
|
|
140
|
+
options[:scopes] = scopes.join(' ')
|
|
141
|
+
end
|
|
142
|
+
post '/users', options
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Prices
|
|
146
|
+
|
|
147
|
+
def buy_price qty=1
|
|
148
|
+
r = get '/prices/buy', {qty: qty}
|
|
149
|
+
r['amount'].to_money(r['currency'])
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def sell_price qty=1
|
|
153
|
+
r = get '/prices/sell', {qty: qty}
|
|
154
|
+
r['amount'].to_money(r['currency'])
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def spot_price currency='USD'
|
|
158
|
+
r = get '/prices/spot_rate', {currency: currency}
|
|
159
|
+
r['amount'].to_money(r['currency'])
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Buys
|
|
163
|
+
|
|
164
|
+
def buy! qty
|
|
165
|
+
r = post '/buys', {qty: qty}
|
|
166
|
+
r = convert_money_objects(r)
|
|
167
|
+
r.transfer.payout_date = Time.parse(r.transfer.payout_date) rescue nil
|
|
168
|
+
r
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Sells
|
|
172
|
+
|
|
173
|
+
def sell! qty
|
|
174
|
+
r = post '/sells', {qty: qty}
|
|
175
|
+
r = convert_money_objects(r)
|
|
176
|
+
r.transfer.payout_date = Time.parse(r.transfer.payout_date) rescue nil
|
|
177
|
+
r
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Transfers
|
|
181
|
+
|
|
182
|
+
def transfers options={}
|
|
183
|
+
r = get '/transfers', options
|
|
184
|
+
r = convert_money_objects(r)
|
|
185
|
+
r.transfers.each do |t|
|
|
186
|
+
t.transfer.payout_date = Time.parse(t.transfer.payout_date) rescue nil
|
|
187
|
+
end
|
|
188
|
+
r
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Wrappers for the main HTTP verbs
|
|
192
|
+
|
|
193
|
+
def get(path, options={})
|
|
194
|
+
http_verb :get, path, options
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def post(path, options={})
|
|
198
|
+
http_verb :post, path, options
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def put(path, options={})
|
|
202
|
+
http_verb :put, path, options
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def delete(path, options={})
|
|
206
|
+
http_verb :delete, path, options
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def self.whitelisted_cert_store
|
|
210
|
+
@@cert_store ||= build_whitelisted_cert_store
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def self.build_whitelisted_cert_store
|
|
214
|
+
path = File.expand_path(File.join(File.dirname(__FILE__), 'ca-coinbase.crt'))
|
|
215
|
+
|
|
216
|
+
certs = [ [] ]
|
|
217
|
+
File.readlines(path).each{|line|
|
|
218
|
+
next if ["\n","#"].include?(line[0])
|
|
219
|
+
certs.last << line
|
|
220
|
+
certs << [] if line == "-----END CERTIFICATE-----\n"
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
result = OpenSSL::X509::Store.new
|
|
224
|
+
|
|
225
|
+
certs.each{|lines|
|
|
226
|
+
next if lines.empty?
|
|
227
|
+
cert = OpenSSL::X509::Certificate.new(lines.join)
|
|
228
|
+
result.add_cert(cert)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
result
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def ssl_options
|
|
235
|
+
{ verify: true, cert_store: self.class.whitelisted_cert_store }
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def http_verb(verb, path, options={})
|
|
239
|
+
|
|
240
|
+
nonce = options[:nonce] || (Time.now.to_f * 1e6).to_i
|
|
241
|
+
|
|
242
|
+
if [:get, :delete].include? verb
|
|
243
|
+
request_options = {}
|
|
244
|
+
path = "#{path}?#{URI.encode_www_form(options)}" if !options.empty?
|
|
245
|
+
hmac_message = nonce.to_s + @base_uri + path
|
|
246
|
+
else
|
|
247
|
+
request_options = {body: options.to_json}
|
|
248
|
+
hmac_message = nonce.to_s + @base_uri + path + options.to_json
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @api_secret, hmac_message)
|
|
252
|
+
|
|
253
|
+
headers = {
|
|
254
|
+
'ACCESS_KEY' => @api_key,
|
|
255
|
+
'ACCESS_SIGNATURE' => signature,
|
|
256
|
+
'ACCESS_NONCE' => nonce.to_s,
|
|
257
|
+
"Content-Type" => "application/json",
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
request_options[:headers] = headers
|
|
261
|
+
|
|
262
|
+
r = self.class.send(verb, path, request_options.merge(ssl_options))
|
|
263
|
+
hash = Hashie::Mash.new(JSON.parse(r.body))
|
|
264
|
+
raise Error.new(hash.error) if hash.error
|
|
265
|
+
raise Error.new(hash.errors.join(", ")) if hash.errors
|
|
266
|
+
hash
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
class Error < StandardError; end
|
|
270
|
+
|
|
271
|
+
private
|
|
272
|
+
|
|
273
|
+
def convert_money_objects obj
|
|
274
|
+
if obj.is_a?(Array)
|
|
275
|
+
obj.each_with_index do |o, i|
|
|
276
|
+
obj[i] = convert_money_objects(o)
|
|
277
|
+
end
|
|
278
|
+
elsif obj.is_a?(Hash)
|
|
279
|
+
if obj[:amount] && (obj[:currency] || obj[:currency_iso])
|
|
280
|
+
obj = obj[:amount].to_money((obj[:currency] || obj[:currency_iso]))
|
|
281
|
+
else
|
|
282
|
+
obj.each do |k,v|
|
|
283
|
+
obj[k] = convert_money_objects(v)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
obj
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'oauth2'
|
|
2
|
+
|
|
3
|
+
module Coinbase
|
|
4
|
+
class OAuthClient < Client
|
|
5
|
+
|
|
6
|
+
AUTHORIZE_URL = 'https://coinbase.com/oauth/authorize'
|
|
7
|
+
TOKEN_URL = 'https://coinbase.com/oauth/token'
|
|
8
|
+
|
|
9
|
+
# Initializes a Coinbase Client using OAuth 2.0 credentials
|
|
10
|
+
#
|
|
11
|
+
# @param [String] client_id this application's Coinbase OAuth2 CLIENT_ID
|
|
12
|
+
# @param [String] client_secret this application's Coinbase OAuth2 CLIENT_SECRET
|
|
13
|
+
# @param [Hash] user_credentials OAuth 2.0 credentials to use
|
|
14
|
+
# @option user_credentials [String] access_token Must pass either this or token
|
|
15
|
+
# @option user_credentials [String] token Must pass either this or access_token
|
|
16
|
+
# @option user_credentials [String] refresh_token Optional
|
|
17
|
+
# @option user_credentials [Integer] expires_at Optional
|
|
18
|
+
# @option user_credentials [Integer] expires_in Optional
|
|
19
|
+
#
|
|
20
|
+
# Please note access tokens will be automatically refreshed when expired
|
|
21
|
+
# Use the credentials method when finished with the client to retrieve up-to-date credentials
|
|
22
|
+
def initialize(client_id, client_secret, user_credentials, options={})
|
|
23
|
+
client_opts = {
|
|
24
|
+
:site => options[:base_uri] || BASE_URI,
|
|
25
|
+
:authorize_url => options[:authorize_url] || AUTHORIZE_URL,
|
|
26
|
+
:token_url => options[:token_url] || TOKEN_URL,
|
|
27
|
+
:ssl => {
|
|
28
|
+
:verify => true,
|
|
29
|
+
:cert_store => ::Coinbase::Client.whitelisted_cert_store
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
@oauth_client = OAuth2::Client.new(client_id, client_secret, client_opts)
|
|
33
|
+
token_hash = user_credentials.dup
|
|
34
|
+
token_hash[:access_token] ||= token_hash[:token]
|
|
35
|
+
token_hash.delete :expires
|
|
36
|
+
raise "No access token provided" unless token_hash[:access_token]
|
|
37
|
+
@oauth_token = OAuth2::AccessToken.from_hash(@oauth_client, token_hash)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def http_verb(verb, path, options={})
|
|
41
|
+
path = remove_leading_slash(path)
|
|
42
|
+
|
|
43
|
+
if [:get, :delete].include? verb
|
|
44
|
+
request_options = {params: options}
|
|
45
|
+
else
|
|
46
|
+
request_options = {headers: {"Content-Type" => "application/json"}, body: options.to_json}
|
|
47
|
+
end
|
|
48
|
+
response = oauth_token.request(verb, path, request_options)
|
|
49
|
+
|
|
50
|
+
hash = Hashie::Mash.new(JSON.parse(response.body))
|
|
51
|
+
raise Error.new(hash.error) if hash.error
|
|
52
|
+
raise Error.new(hash.errors.join(", ")) if hash.errors
|
|
53
|
+
hash
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def refresh!
|
|
57
|
+
raise "Access token not initialized." unless @oauth_token
|
|
58
|
+
@oauth_token = @oauth_token.refresh!
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def oauth_token
|
|
62
|
+
raise "Access token not initialized." unless @oauth_token
|
|
63
|
+
refresh! if @oauth_token.expired?
|
|
64
|
+
@oauth_token
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def credentials
|
|
68
|
+
@oauth_token.to_hash
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def remove_leading_slash(path)
|
|
74
|
+
path.sub(/^\//, '')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/coinbase.rb
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
require_relative 'coinbase/constants'
|
|
7
|
-
require_relative 'coinbase/network'
|
|
8
|
-
require_relative 'coinbase/transfer'
|
|
9
|
-
require_relative 'coinbase/wallet'
|
|
10
|
-
require 'dotenv'
|
|
11
|
-
|
|
12
|
-
# The Coinbase SDK.
|
|
13
|
-
module Coinbase
|
|
14
|
-
# Initializes the Coinbase SDK.
|
|
15
|
-
def self.init
|
|
16
|
-
Dotenv.load
|
|
17
|
-
end
|
|
18
|
-
end
|
|
1
|
+
require "json"
|
|
2
|
+
require "money"
|
|
3
|
+
require "coinbase/version"
|
|
4
|
+
require "coinbase/client"
|
|
5
|
+
require "coinbase/oauth_client"
|