Class: Coinkite::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/coinkite.rb

Constant Summary collapse

DEFAULT_CA_BUNDLE_PATH =
File.dirname(__FILE__) + '/data/ca-certificates.crt'

Instance Method Summary collapse

Constructor Details

#initialize(api_key, api_secret) ⇒ Client

Returns a new instance of Client.



16
17
18
19
20
21
# File 'lib/coinkite.rb', line 16

def initialize(api_key, api_secret)
  @api_key = api_key
  @api_secret = api_secret
  @api_base = 'https://api.coinkite.com'
  @ssl_bundle_path  = DEFAULT_CA_BUNDLE_PATH
end

Instance Method Details

#api_url(url = '') ⇒ Object



23
24
25
# File 'lib/coinkite.rb', line 23

def api_url(url='')
  @api_base + url
end

#get(endpoint, *args) ⇒ Object



150
151
152
# File 'lib/coinkite.rb', line 150

def get(endpoint, *args)
  request('GET', endpoint, *args)
end

#get_accountsObject



154
155
156
# File 'lib/coinkite.rb', line 154

def get_accounts
  get('/v1/my/accounts')["results"]
end

#get_balance(account) ⇒ Object



162
163
164
# File 'lib/coinkite.rb', line 162

def get_balance()
  get("/v1/account/#{account}")["account"]
end

#get_detail(refnum) ⇒ Object



158
159
160
# File 'lib/coinkite.rb', line 158

def get_detail(refnum)
  get("/v1/detail/#{refnum}")["detail"]
end

#get_iter(endpoint, offset: 0, limit: nil, batch_size: 25, safety_limit: 500, **options) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/coinkite.rb', line 166

def get_iter(endpoint, offset: 0, limit: nil, batch_size: 25, safety_limit: 500, **options)
  Enumerator.new do |yielder|
    loop do
      if limit and limit < batch_size
        batch_size = limit
      end

      response = get(endpoint, { offset: offset, limit: batch_size })

      here = response["paging"]["count_here"]
      total = response["paging"]["total_count"]

      if total > safety_limit
        raise StandardError.new("Too many results (#{total}); consider another approach")
      end

      raise StopIteration if not here

      response["results"].each { |entry| yielder.yield entry }

      offset += here
      if limit != nil
        limit -= here
        raise StopIteration if limit <= 0
      end
    end
  end
end

#get_list(what) ⇒ Object



195
196
197
198
199
# File 'lib/coinkite.rb', line 195

def get_list(what)
  # this returns a generator function
  endpoint = "/v1/list/#{what}"
  get_iter(endpoint)
end

#handle_api_error(rcode, rbody) ⇒ Object



120
121
122
123
124
125
126
127
128
129
# File 'lib/coinkite.rb', line 120

def handle_api_error(rcode, rbody)
  case rcode
  when 400
    raise CoinkiteError.new(rbody)
  when 404
    raise CoinkiteError.new(rbody)
  else
    raise CoinkiteError.new(rbody)
  end
end

#handle_restclient_error(e) ⇒ Object

Raises:



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/coinkite.rb', line 92

def handle_restclient_error(e)
  case e
  when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
    message = "Could not connect to Coinkite (#{@api_base}). " +
      "Please check your internet connection and try again. " +
      "If this problem persists, you should check Coinkite's service status at " +
      "https://twitter.com/Coinkite, or let us know at [email protected]."

  when RestClient::SSLCertificateNotVerified
    message = "Could not verify Coinkite's SSL certificate. " +
      "Please make sure that your network is not intercepting certificates. " +
      "(Try going to https://api.coinkite.com in your browser.) " +
      "If this problem persists, let us know at [email protected]."

  when SocketError
    message = "Unexpected error communicating when trying to connect to Coinkite. " +
      "You may be seeing this message because your DNS is not working. " +
      "To check, try running 'host coinkite.com' from the command line."

  else
    message = "Unexpected error communicating with Coinkite. " +
      "If this problem persists, let us know at [email protected]."

  end

  raise APIConnectionError.new(message + "\n\n(Network error: #{e.message})")
end

#make_signature(endpoint, force_ts = nil) ⇒ Object



131
132
133
134
135
136
137
# File 'lib/coinkite.rb', line 131

def make_signature(endpoint, force_ts=nil)
  ts = force_ts || Time.now.utc.iso8601
  data = endpoint + '|' + ts
  hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA256'), @api_secret, data)

  return hmac, ts
end

#request(method, endpoint, params = {}, headers = {}) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/coinkite.rb', line 27

def request(method, endpoint, params={}, headers={})
  unless api_key ||= @api_key
    raise AuthenticationError.new('No API key provided. ' +
      'Set your API key using "Coinkite.api_key = <API-KEY>". ' +
      'You can generate API keys from the Coinkite web interface. ')
  end

  url = api_url(endpoint)

  case method.to_s.downcase.to_sym
  when :get, :head, :delete
    url += "#{URI.parse(url).query ? '&' : '?'}#{uri_encode(params)}" if params && params.any?
    payload = nil
  else
    payload = JSON.encode(params.fetch('_data', params))
    headers['Content-Type'] = 'application/json'
  end

  request_opts = {}
  request_opts.update(:verify_ssl => OpenSSL::SSL::VERIFY_PEER,
                    :ssl_ca_file => @ssl_bundle_path,
                    :method => method, :open_timeout => 30,
                    :payload => payload, :url => url, :timeout => 80)

  begin
    request_opts.update(:headers => request_headers(endpoint).update(headers))
    response = RestClient::Request.execute(request_opts)
  rescue SocketError => e
    handle_restclient_error(e)
  rescue NoMethodError => e
    # Work around RestClient bug
    if e.message =~ /\WRequestFailed\W/
      e = APIConnectionError.new('Unexpected HTTP response code')
      handle_restclient_error(e)
    else
      raise
    end
  rescue RestClient::ExceptionWithResponse => e
    if rcode = e.http_code and rbody = e.http_body
      rbody = JSON.parse(rbody)
      if rcode == 429 and rbody.has_key?("wait_time")
        sleep(rbody["wait_time"])
        retry
      else
        handle_api_error(rcode, rbody)
      end
    else
      handle_restclient_error(e)
    end
  rescue RestClient::Exception, Errno::ECONNREFUSED => e
    handle_restclient_error(e)
  end

  #puts response.body
  JSON.parse(response.body)
end

#request_headers(endpoint, force_ts = nil) ⇒ Object



139
140
141
142
143
144
145
146
147
148
# File 'lib/coinkite.rb', line 139

def request_headers(endpoint, force_ts=nil)
  signature, timestamp = make_signature(endpoint, force_ts)

  {
    'X-CK-Key' => @api_key,
    'X-CK-Timestamp' => timestamp,
    'X-CK-Sign' => signature,
    'User-Agent' => "Coinkite/v1 RubyBindings/#{Coinkite::VERSION}"
  }
end

#uri_encode(params) ⇒ Object



84
85
86
# File 'lib/coinkite.rb', line 84

def uri_encode(params)
  params.map { |k,v| "#{k}=#{url_encode(v)}" }.join('&')
end

#url_encode(key) ⇒ Object



88
89
90
# File 'lib/coinkite.rb', line 88

def url_encode(key)
  URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
end