coinkite 0.9
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 +3 -0
- data/Gemfile +3 -0
- data/README.md +62 -0
- data/coinkite.gemspec +24 -0
- data/lib/coinkite/errors/api_connection_error.rb +21 -0
- data/lib/coinkite/errors/coinkite_error.rb +32 -0
- data/lib/coinkite/version.rb +3 -0
- data/lib/coinkite.rb +165 -0
- data/lib/data/ca-certificates.crt +5165 -0
- data/sign.rb +23 -0
- data/spec/coinkite_spec.rb +11 -0
- data/spec/spec_helper.rb +9 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f9e4b935ab46f376d664cd1d96c0b29ccf0cbe1a
|
4
|
+
data.tar.gz: a0b409931a40c8522e174eab4b81032768f287ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 29d35f924d8d07e68585512e75ede9ff96594d940597fa68171f5154d9eeabb563e3d255330b35ef6fac75493e1e11bb064066c2763d5afefbbf689bd6374377
|
7
|
+
data.tar.gz: e98d7d1636cdefdf94142bc3e3c464290c759f3b1d24102383022071a49d79f4138f63a00818117c93727452bdc558d68b57acc6e80618c0818cd7589aefe3dd
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Coinkite API Code for Ruby
|
2
|
+
|
3
|
+
[Learn more about Coinkite's API here](https://docs.coinkite.com/)
|
4
|
+
and visit the [Coinkite Main Site](https://coinkite.com/) to open your
|
5
|
+
account today!
|
6
|
+
|
7
|
+
## Introduction
|
8
|
+
|
9
|
+
Every request made to the Coinkite API requires three special header
|
10
|
+
lines. This code can generate the timestamp and signature value
|
11
|
+
required for 2 of those 3 headers. The remaining header is just
|
12
|
+
the API key itself.
|
13
|
+
|
14
|
+
Header lines you need:
|
15
|
+
|
16
|
+
X-CK-Key: K5555a555-55a555aa-a55aa5a5555aaa5a
|
17
|
+
X-CK-Timestamp: 2014-06-23T03:10:04.905376
|
18
|
+
X-CK-Sign: 0aa7755aaa45189a98a5a8a887a564aa55aa5aa4aa7a98aa2858aaa60a5a56aa
|
19
|
+
|
20
|
+
## How to Install
|
21
|
+
|
22
|
+
Replace the two values shown here.
|
23
|
+
|
24
|
+
````ruby
|
25
|
+
API_KEY = 'this-is-my-key'
|
26
|
+
API_SECRET = 'this-is-my-secret'
|
27
|
+
````
|
28
|
+
|
29
|
+
The keys you need can be created on
|
30
|
+
[Coinkite.com under Merchant / API]([https://coinkite.com/merchant/api).
|
31
|
+
|
32
|
+
|
33
|
+
## More about Coinkite
|
34
|
+
|
35
|
+
Coinkite is the world's easiest and most powerful web wallet for
|
36
|
+
safely holding all your cryptocurrencies, including Bitcoin and Litecoin.
|
37
|
+
|
38
|
+
[Learn more about all we offer](https://coinkite.com/)
|
39
|
+
|
40
|
+
## Liscense
|
41
|
+
|
42
|
+
Copyright (C) 2013 Coinkite Inc. (https://coinkite.com)
|
43
|
+
|
44
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
45
|
+
of this software and associated documentation files (the "Software"), to deal
|
46
|
+
in the Software without restriction, including without limitation the rights
|
47
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
48
|
+
copies of the Software, and to permit persons to whom the Software is
|
49
|
+
furnished to do so, subject to the following conditions:
|
50
|
+
|
51
|
+
The above copyright notice and this permission notice shall be included in
|
52
|
+
all copies or substantial portions of the Software.
|
53
|
+
|
54
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
55
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
56
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
57
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
58
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
59
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
60
|
+
THE SOFTWARE.
|
61
|
+
|
62
|
+
|
data/coinkite.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
|
3
|
+
require 'coinkite/version'
|
4
|
+
|
5
|
+
spec = Gem::Specification.new do |s|
|
6
|
+
s.name = 'coinkite'
|
7
|
+
s.version = Coinkite::VERSION
|
8
|
+
s.summary = 'Ruby bindings for the Coinkite API'
|
9
|
+
s.description = 'Coinkite is the most secure way to transact in bitcoin online. See https://coinkite.com for details.'
|
10
|
+
s.authors = ['Ilia Lobsanov']
|
11
|
+
s.email = ['ilia@lobsanov.com']
|
12
|
+
s.homepage = 'https://docs.coinkite.com/api/index.html'
|
13
|
+
s.license = 'MIT'
|
14
|
+
|
15
|
+
s.add_dependency('rest-client', '~> 1.4')
|
16
|
+
s.add_dependency('mime-types', '>= 1.25', '< 3.0')
|
17
|
+
s.add_dependency('json', '~> 1.8')
|
18
|
+
|
19
|
+
s.add_development_dependency('rspec', '~>3.0')
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
23
|
+
s.require_paths = ['lib']
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Coinkite
|
2
|
+
class APIConnectionError < StandardError
|
3
|
+
attr_reader :message
|
4
|
+
attr_reader :http_status
|
5
|
+
attr_reader :http_body
|
6
|
+
attr_reader :json_body
|
7
|
+
|
8
|
+
def initialize(message=nil, http_status=nil, http_body=nil, json_body=nil)
|
9
|
+
@message = message
|
10
|
+
@http_status = http_status
|
11
|
+
@http_body = http_body
|
12
|
+
@json_body = json_body
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
|
17
|
+
"#{status_string}#{@message}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Coinkite
|
2
|
+
class CoinkiteError < StandardError
|
3
|
+
attr_reader :message
|
4
|
+
attr_reader :http_status
|
5
|
+
attr_reader :http_body
|
6
|
+
attr_reader :json_body
|
7
|
+
|
8
|
+
def initialize(message=nil, http_status=nil, http_body=nil, json_body=nil)
|
9
|
+
@message = message
|
10
|
+
@http_status = http_status
|
11
|
+
@http_body = http_body
|
12
|
+
@json_body = json_body
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
|
17
|
+
"#{status_string}#{@message}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class CKServerSideError < CoinkiteError
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
class CKMissingError < CoinkiteError
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class CKArgumentError < CoinkiteError
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
data/lib/coinkite.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'time'
|
3
|
+
require 'openssl'
|
4
|
+
require 'rest-client'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
require 'coinkite/version'
|
8
|
+
|
9
|
+
require 'coinkite/errors/api_connection_error'
|
10
|
+
require 'coinkite/errors/coinkite_error'
|
11
|
+
|
12
|
+
module Coinkite
|
13
|
+
class Client
|
14
|
+
DEFAULT_CA_BUNDLE_PATH = File.dirname(__FILE__) + '/data/ca-certificates.crt'
|
15
|
+
|
16
|
+
def initialize(api_key, api_secret)
|
17
|
+
@api_key = api_key
|
18
|
+
@api_secret = api_secret
|
19
|
+
@api_base = 'https://api.coinkite.com'
|
20
|
+
@ssl_bundle_path = DEFAULT_CA_BUNDLE_PATH
|
21
|
+
end
|
22
|
+
|
23
|
+
def api_url(url='')
|
24
|
+
@api_base + url
|
25
|
+
end
|
26
|
+
|
27
|
+
def request(method, endpoint, params={}, headers={})
|
28
|
+
unless api_key ||= @api_key
|
29
|
+
raise AuthenticationError.new('No API key provided. ' +
|
30
|
+
'Set your API key using "Coinkite.api_key = <API-KEY>". ' +
|
31
|
+
'You can generate API keys from the Coinkite web interface. ')
|
32
|
+
end
|
33
|
+
|
34
|
+
url = api_url(endpoint)
|
35
|
+
|
36
|
+
case method.to_s.downcase.to_sym
|
37
|
+
when :get, :head, :delete
|
38
|
+
url += "#{URI.parse(url).query ? '&' : '?'}#{uri_encode(params)}" if params && params.any?
|
39
|
+
payload = nil
|
40
|
+
else
|
41
|
+
payload = JSON.encode(params.fetch('_data', params))
|
42
|
+
headers['Content-Type'] = 'application/json'
|
43
|
+
end
|
44
|
+
|
45
|
+
request_opts = {}
|
46
|
+
request_opts.update(:verify_ssl => OpenSSL::SSL::VERIFY_PEER,
|
47
|
+
:ssl_ca_file => @ssl_bundle_path,
|
48
|
+
:headers => request_headers(endpoint).update(headers),
|
49
|
+
:method => method, :open_timeout => 30,
|
50
|
+
:payload => payload, :url => url, :timeout => 80)
|
51
|
+
|
52
|
+
begin
|
53
|
+
response = RestClient::Request.execute(request_opts)
|
54
|
+
rescue SocketError => e
|
55
|
+
handle_restclient_error(e)
|
56
|
+
rescue NoMethodError => e
|
57
|
+
# Work around RestClient bug
|
58
|
+
if e.message =~ /\WRequestFailed\W/
|
59
|
+
e = APIConnectionError.new('Unexpected HTTP response code')
|
60
|
+
handle_restclient_error(e)
|
61
|
+
else
|
62
|
+
raise
|
63
|
+
end
|
64
|
+
rescue RestClient::ExceptionWithResponse => e
|
65
|
+
if rcode = e.http_code and rbody = e.http_body
|
66
|
+
if rcode == 429 and rbody.has_key?("wait_time")
|
67
|
+
sleep(rbody.wait_time)
|
68
|
+
retry
|
69
|
+
else
|
70
|
+
handle_api_error(rcode, rbody)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
handle_restclient_error(e)
|
74
|
+
end
|
75
|
+
rescue RestClient::Exception, Errno::ECONNREFUSED => e
|
76
|
+
handle_restclient_error(e)
|
77
|
+
end
|
78
|
+
|
79
|
+
JSON.parse(response.body)
|
80
|
+
end
|
81
|
+
|
82
|
+
def uri_encode(params)
|
83
|
+
params.map { |k,v| "#{k}=#{url_encode(v)}" }.join('&')
|
84
|
+
end
|
85
|
+
|
86
|
+
def url_encode(key)
|
87
|
+
URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
88
|
+
end
|
89
|
+
|
90
|
+
def handle_restclient_error(e)
|
91
|
+
case e
|
92
|
+
when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
|
93
|
+
message = "Could not connect to Coinkite (#{@api_base}). " +
|
94
|
+
"Please check your internet connection and try again. " +
|
95
|
+
"If this problem persists, you should check Coinkite's service status at " +
|
96
|
+
"https://twitter.com/Coinkite, or let us know at support@coinkite.com."
|
97
|
+
|
98
|
+
when RestClient::SSLCertificateNotVerified
|
99
|
+
message = "Could not verify Coinkite's SSL certificate. " +
|
100
|
+
"Please make sure that your network is not intercepting certificates. " +
|
101
|
+
"(Try going to https://api.coinkite.com in your browser.) " +
|
102
|
+
"If this problem persists, let us know at support@coinkite.com."
|
103
|
+
|
104
|
+
when SocketError
|
105
|
+
message = "Unexpected error communicating when trying to connect to Coinkite. " +
|
106
|
+
"You may be seeing this message because your DNS is not working. " +
|
107
|
+
"To check, try running 'host coinkite.com' from the command line."
|
108
|
+
|
109
|
+
else
|
110
|
+
message = "Unexpected error communicating with Coinkite. " +
|
111
|
+
"If this problem persists, let us know at support@coinkite.com."
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
raise APIConnectionError.new(message + "\n\n(Network error: #{e.message})")
|
116
|
+
end
|
117
|
+
|
118
|
+
def handle_api_error(rcode, rbody)
|
119
|
+
case rcode
|
120
|
+
when 400
|
121
|
+
raise CoinkiteError.new(rbody)
|
122
|
+
when 404
|
123
|
+
raise CoinkiteError.new(rbody)
|
124
|
+
else
|
125
|
+
raise CoinkiteError.new(rbody)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def make_signature(endpoint, force_ts=nil)
|
130
|
+
ts = force_ts || Time.now.utc.iso8601
|
131
|
+
data = endpoint + '|' + ts
|
132
|
+
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA256'), @api_secret, data)
|
133
|
+
|
134
|
+
return hmac, ts
|
135
|
+
end
|
136
|
+
|
137
|
+
def request_headers(endpoint, force_ts=nil)
|
138
|
+
signature, timestamp = make_signature(endpoint, force_ts)
|
139
|
+
|
140
|
+
{
|
141
|
+
'X-CK-Key' => @api_key,
|
142
|
+
'X-CK-Timestamp' => timestamp,
|
143
|
+
'X-CK-Sign' => signature,
|
144
|
+
'User-Agent' => "Coinkite/v1 RubyBindings/#{Coinkite::VERSION}"
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
def get(endpoint)
|
149
|
+
request('GET', endpoint)
|
150
|
+
end
|
151
|
+
|
152
|
+
def get_accounts
|
153
|
+
get('/v1/my/accounts')["results"]
|
154
|
+
end
|
155
|
+
|
156
|
+
def get_detail(refnum)
|
157
|
+
get("/v1/detail/#{refnum}")["detail"]
|
158
|
+
end
|
159
|
+
|
160
|
+
def get_balance(account)
|
161
|
+
get("/v1/account/#{account}")["account"]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|