coinbase 0.0.1 → 4.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +7 -0
  4. data/CONTRIBUTING.md +53 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +201 -0
  7. data/README.md +621 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/coinbase.gemspec +26 -0
  12. data/lib/coinbase/util.rb +16 -0
  13. data/lib/coinbase/wallet/adapters/em_http.rb +78 -0
  14. data/lib/coinbase/wallet/adapters/net_http.rb +68 -0
  15. data/lib/coinbase/wallet/api_client.rb +755 -0
  16. data/lib/coinbase/wallet/api_errors.rb +120 -0
  17. data/lib/coinbase/wallet/api_response.rb +41 -0
  18. data/lib/coinbase/wallet/ca-coinbase.crt +101 -0
  19. data/lib/coinbase/wallet/client.rb +101 -0
  20. data/lib/coinbase/wallet/coinbase-callback.pub +14 -0
  21. data/lib/coinbase/wallet/models/account.rb +193 -0
  22. data/lib/coinbase/wallet/models/address.rb +12 -0
  23. data/lib/coinbase/wallet/models/api_object.rb +46 -0
  24. data/lib/coinbase/wallet/models/checkout.rb +19 -0
  25. data/lib/coinbase/wallet/models/order.rb +12 -0
  26. data/lib/coinbase/wallet/models/transaction.rb +21 -0
  27. data/lib/coinbase/wallet/models/transfer.rb +13 -0
  28. data/lib/coinbase/wallet/models/user.rb +15 -0
  29. data/lib/coinbase/wallet/version.rb +5 -0
  30. data/lib/coinbase/wallet.rb +24 -156
  31. data/spec/account_spec.rb +199 -0
  32. data/spec/callback_signature_verification_spec.rb +16 -0
  33. data/spec/clients/client_spec.rb +34 -0
  34. data/spec/clients/oauth_client_spec.rb +56 -0
  35. data/spec/endpoints_spec.rb +352 -0
  36. data/spec/error_spec.rb +137 -0
  37. data/spec/models/address_spec.rb +26 -0
  38. data/spec/models/api_object_spec.rb +70 -0
  39. data/spec/models/checkout_spec.rb +52 -0
  40. data/spec/models/current_user_spec.rb +29 -0
  41. data/spec/models/order_spec.rb +52 -0
  42. data/spec/models/request_spec.rb +47 -0
  43. data/spec/models/transfer_spec.rb +64 -0
  44. data/spec/models/user_spec.rb +24 -0
  45. data/spec/spec_helper.rb +13 -0
  46. metadata +67 -112
  47. data/lib/coinbase/address.rb +0 -127
  48. data/lib/coinbase/asset.rb +0 -20
  49. data/lib/coinbase/balance_map.rb +0 -48
  50. data/lib/coinbase/constants.rb +0 -38
  51. data/lib/coinbase/network.rb +0 -55
  52. data/lib/coinbase/transfer.rb +0 -153
  53. data/lib/coinbase.rb +0 -18
@@ -0,0 +1,120 @@
1
+ module Coinbase
2
+ module Wallet
3
+ def self.format_error(resp)
4
+ error = resp.body && (resp.body['errors'] || resp.body['warnings']).first
5
+ return resp.body unless error
6
+ message = error['message']
7
+ message += " (#{error['url']})" if error["url"]
8
+ message
9
+ end
10
+
11
+ def self.check_response_status(resp)
12
+ (resp.body['warnings'] || []).each do |warning|
13
+ message = "WARNING: #{warning['message']}"
14
+ message += " (#{warning['url']})" if warning["url"]
15
+ $stderr.puts message
16
+ end
17
+
18
+ # OAuth2 errors
19
+ if resp.status >= 400 && resp.body['error']
20
+ raise APIError, resp.body['error_description']
21
+ end
22
+
23
+ # Regular errors
24
+ if resp.body['errors']
25
+ case resp.status
26
+ when 400
27
+ case resp.body['errors'].first['id']
28
+ when 'param_required' then raise ParamRequiredError, format_error(resp)
29
+ when 'invalid_request' then raise InvalidRequestError, format_error(resp)
30
+ when 'personal_details_required' then raise PersonalDetailsRequiredError, format_error(resp)
31
+ end
32
+ raise BadRequestError, format_error(resp)
33
+ when 401
34
+ case resp.body['errors'].first['id']
35
+ when 'authentication_error' then raise AuthenticationError, format_error(resp)
36
+ when 'unverified_email' then raise UnverifiedEmailError, format_error(resp)
37
+ when 'invalid_token' then raise InvalidTokenError, format_error(resp)
38
+ when 'revoked_token' then raise RevokedTokenError, format_error(resp)
39
+ when 'expired_token' then raise ExpiredTokenError, format_error(resp)
40
+ end
41
+ raise AuthenticationError, format_error(resp)
42
+ when 402 then raise TwoFactorRequiredError, format_error(resp)
43
+ when 403 then raise InvalidScopeError, format_error(resp)
44
+ when 404 then raise NotFoundError, format_error(resp)
45
+ when 422 then raise ValidationError, format_error(resp)
46
+ when 429 then raise RateLimitError, format_error(resp)
47
+ when 500 then raise InternalServerError, format_error(resp)
48
+ when 503 then raise ServiceUnavailableError, format_error(resp)
49
+ end
50
+ end
51
+
52
+ if resp.status > 400
53
+ raise APIError, "[#{resp.status}] #{resp.body}"
54
+ end
55
+ end
56
+
57
+ #
58
+ # Rest API Errors
59
+ #
60
+ class APIError < RuntimeError
61
+ end
62
+
63
+ # Status 400
64
+ class BadRequestError < APIError
65
+ end
66
+
67
+ class ParamRequiredError < APIError
68
+ end
69
+
70
+ class InvalidRequestError < APIError
71
+ end
72
+
73
+ class PersonalDetailsRequiredError < APIError
74
+ end
75
+
76
+ # Status 401
77
+ class AuthenticationError < APIError
78
+ end
79
+
80
+ class UnverifiedEmailError < APIError
81
+ end
82
+
83
+ class InvalidTokenError < APIError
84
+ end
85
+
86
+ class RevokedTokenError < APIError
87
+ end
88
+
89
+ class ExpiredTokenError < APIError
90
+ end
91
+
92
+ # Status 402
93
+ class TwoFactorRequiredError < APIError
94
+ end
95
+
96
+ # Status 403
97
+ class InvalidScopeError < APIError
98
+ end
99
+
100
+ # Status 404
101
+ class NotFoundError < APIError
102
+ end
103
+
104
+ # Status 422
105
+ class ValidationError < APIError
106
+ end
107
+
108
+ # Status 429
109
+ class RateLimitError < APIError
110
+ end
111
+
112
+ # Status 500
113
+ class InternalServerError < APIError
114
+ end
115
+
116
+ # Status 503
117
+ class ServiceUnavailableError < APIError
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,41 @@
1
+ module Coinbase
2
+ module Wallet
3
+ # Encapsulate data for an API response
4
+ class APIResponse
5
+ attr_reader :received_at
6
+ attr_accessor :client
7
+ attr_accessor :method
8
+ attr_accessor :params
9
+
10
+ def initialize(resp)
11
+ @received_at = Time.now
12
+ @response = resp
13
+ end
14
+
15
+ def raw
16
+ @response
17
+ end
18
+
19
+ def body
20
+ raise NotImplementedError
21
+ end
22
+ alias_method :data, :body
23
+
24
+ def body=(body)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def headers
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def status
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def has_more?
37
+ body.has_key?('pagination') && body['pagination']['next_uri'] != nil
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,101 @@
1
+ ## DigiCert High Assurance EV Root CA
2
+ -----BEGIN CERTIFICATE-----
3
+ MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
4
+ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
5
+ d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
6
+ ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
7
+ MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
8
+ LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
9
+ RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
10
+ +9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
11
+ PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
12
+ xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
13
+ Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
14
+ hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
15
+ EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
16
+ MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
17
+ FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
18
+ nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
19
+ eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
20
+ hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
21
+ Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
22
+ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
23
+ +OkuE6N36B9K
24
+ -----END CERTIFICATE-----
25
+
26
+ ## DigiCert Global Root CA
27
+ -----BEGIN CERTIFICATE-----
28
+ MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
29
+ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
30
+ d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
31
+ QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
32
+ MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
33
+ b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
34
+ 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
35
+ CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
36
+ nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
37
+ 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
38
+ T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
39
+ gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
40
+ BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
41
+ TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
42
+ DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
43
+ hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
44
+ 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
45
+ PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
46
+ YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
47
+ CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
48
+ -----END CERTIFICATE-----
49
+
50
+ ## VeriSign Class 3 Primary CA - G5 (Backup)
51
+ -----BEGIN CERTIFICATE-----
52
+ MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
53
+ yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
54
+ ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
55
+ U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
56
+ ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
57
+ aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
58
+ MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
59
+ ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
60
+ biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
61
+ U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
62
+ aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
63
+ nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
64
+ t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
65
+ SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
66
+ BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
67
+ rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
68
+ NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
69
+ BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
70
+ BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
71
+ aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
72
+ MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
73
+ p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
74
+ 5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
75
+ WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
76
+ 4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
77
+ hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
78
+ -----END CERTIFICATE-----
79
+
80
+ ## DigiCert Baltimore CyberTrust Root
81
+ -----BEGIN CERTIFICATE-----
82
+ MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
83
+ RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
84
+ VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
85
+ DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
86
+ ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
87
+ VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
88
+ mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
89
+ IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
90
+ mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
91
+ XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
92
+ dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
93
+ jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
94
+ BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
95
+ DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
96
+ 9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
97
+ jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
98
+ Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
99
+ ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
100
+ R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
101
+ -----END CERTIFICATE-----
@@ -0,0 +1,101 @@
1
+ module Coinbase
2
+ module Wallet
3
+ BASE_API_URL = "https://api.coinbase.com"
4
+ API_VERSION = '2015-06-16'
5
+
6
+ class Client < NetHTTPClient
7
+ def initialize(options={})
8
+ [ :api_key, :api_secret ].each do |opt|
9
+ raise unless options.has_key? opt
10
+ end
11
+ @api_key = options[:api_key]
12
+ @api_secret = options[:api_secret]
13
+ @api_uri = URI.parse(options[:api_url] || BASE_API_URL)
14
+ super(@api_uri, options)
15
+ end
16
+
17
+ def auth_headers(method, path, body)
18
+ ts = Time.now.to_i.to_s
19
+ signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'),
20
+ @api_secret,
21
+ ts + method + path + body.to_s)
22
+ { 'CB-ACCESS-KEY' => @api_key,
23
+ 'CB-ACCESS-SIGN' => signature,
24
+ 'CB-ACCESS-TIMESTAMP' => ts,
25
+ 'CB-VERSION' => API_VERSION }
26
+ end
27
+ end
28
+
29
+ class OAuthClient < NetHTTPClient
30
+ attr_accessor :access_token, :refresh_token
31
+
32
+ def initialize(options={})
33
+ raise unless options.has_key? :access_token
34
+ @access_token = options[:access_token]
35
+ @refresh_token = options[:refresh_token]
36
+ @oauth_uri = URI.parse(options[:api_url] || BASE_API_URL)
37
+ super(@oauth_uri, options)
38
+ end
39
+
40
+ def auth_headers(method, path, body)
41
+ { 'Authorization' => "Bearer #{@access_token}",
42
+ 'CB-VERSION' => API_VERSION }
43
+ end
44
+
45
+ def authorize!(redirect_url, params = {})
46
+ raise NotImplementedError
47
+ end
48
+
49
+ def revoke!(params = {})
50
+ params[:token] ||= @access_token
51
+
52
+ out = nil
53
+ post("/oauth/revoke", params) do |resp|
54
+ out = APIObject.new(self, resp.body)
55
+ yield(out, resp) if block_given?
56
+ end
57
+ out
58
+ end
59
+
60
+ def refresh!(params = {})
61
+ params[:grant_type] = 'refresh_token'
62
+ params[:refresh_token] ||= @refresh_token
63
+
64
+ raise "Missing Parameter: refresh_token" unless params.has_key?(:refresh_token)
65
+
66
+ out = nil
67
+ post("/oauth/token", params) do |resp|
68
+ out = APIObject.new(self, resp.body)
69
+ # Update tokens to current instance
70
+ # Developer should always persist them
71
+ @access_token = out.access_token
72
+ @refresh_token = out.refresh_token
73
+ yield(out, resp) if block_given?
74
+ end
75
+ out
76
+ end
77
+ end
78
+
79
+ class AsyncClient < EMHTTPClient
80
+ def initialize(options={})
81
+ [ :api_key, :api_secret ].each do |opt|
82
+ raise unless options.has_key? opt
83
+ end
84
+ @api_key = options[:api_key]
85
+ @api_secret = options[:api_secret]
86
+ @api_uri = URI.parse(options[:api_url] || BASE_API_URL)
87
+ end
88
+
89
+ def auth_headers(method, path, body)
90
+ ts = Time.now.to_i.to_s
91
+ signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'),
92
+ @api_secret,
93
+ ts + method + path + body.to_s)
94
+ { 'CB-ACCESS-KEY' => @api_key,
95
+ 'CB-ACCESS-SIGN' => signature,
96
+ 'CB-ACCESS-TIMESTAMP' => ts,
97
+ 'CB-VERSION' => API_VERSION }
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,14 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9MsJBuXzFGIh/xkAA9Cy
3
+ QdZKRerV+apyOAWY7sEYV/AJg+AX/tW2SHeZj+3OilNYm5DlBi6ZzDboczmENrFn
4
+ mUXQsecsR5qjdDWb2qYqBkDkoZP02m9o9UmKObR8coKW4ZBw0hEf3fP9OEofG2s7
5
+ Z6PReWFyQffnnecwXJoN22qjjsUtNNKOOo7/l+IyGMVmdzJbMWQS4ybaU9r9Ax0J
6
+ 4QUJSS/S4j4LP+3Z9i2DzIe4+PGa4Nf7fQWLwE45UUp5SmplxBfvEGwYNEsHvmRj
7
+ usIy2ZunSO2CjJ/xGGn9+/57W7/SNVzk/DlDWLaN27hUFLEINlWXeYLBPjw5GGWp
8
+ ieXGVcTaFSLBWX3JbOJ2o2L4MxinXjTtpiKjem9197QXSVZ/zF1DI8tRipsgZWT2
9
+ /UQMqsJoVRXHveY9q9VrCLe97FKAUiohLsskr0USrMCUYvLU9mMw15hwtzZlKY8T
10
+ dMH2Ugqv/CPBuYf1Bc7FAsKJwdC504e8kAUgomi4tKuUo25LPZJMTvMTs/9IsRJv
11
+ I7ibYmVR3xNsVEpupdFcTJYGzOQBo8orHKPFn1jj31DIIKociCwu6m8ICDgLuMHj
12
+ 7bUHIlTzPPT7hRPyBQ1KdyvwxbguqpNhqp1hG2sghgMr0M6KMkUEz38JFElsVrpF
13
+ 4z+EqsFcIZzjkSG16BjjjTkCAwEAAQ==
14
+ -----END PUBLIC KEY-----
@@ -0,0 +1,193 @@
1
+ module Coinbase
2
+ module Wallet
3
+ class Account < APIObject
4
+ def update!(params = {})
5
+ @client.update_account(self['id'], params) do |data, resp|
6
+ update(data)
7
+ yield(data, resp) if block_given?
8
+ end
9
+ end
10
+
11
+ def make_primary!(params = {})
12
+ @client.set_primary_account(self['id'], params) do |data, resp|
13
+ update(data)
14
+ yield(data, resp) if block_given?
15
+ end
16
+ end
17
+
18
+ def delete!(params = {})
19
+ @client.delete_account(self['id'], params) do |data, resp|
20
+ yield(data, resp) if block_given?
21
+ end
22
+ end
23
+
24
+ #
25
+ # Addresses
26
+ #
27
+ def addresses(params = {})
28
+ @client.addresses(self['id'], params) do |data, resp|
29
+ yield(data, resp) if block_given?
30
+ end
31
+ end
32
+
33
+ def address(address_id, params = {})
34
+ @client.address(self['id'], address_id, params) do |data, resp|
35
+ yield(data, resp) if block_given?
36
+ end
37
+ end
38
+
39
+ def address_transactions(address_id, params = {})
40
+ @client.address_transactions(self['id'], address_id, params) do |data, resp|
41
+ yield(data, resp) if block_given?
42
+ end
43
+ end
44
+
45
+ def create_address(params = {})
46
+ @client.create_address(self['id'], params) do |data, resp|
47
+ yield(data, resp) if block_given?
48
+ end
49
+ end
50
+
51
+ #
52
+ # Transactions
53
+ #
54
+ def transactions(params = {})
55
+ @client.transactions(self['id'], params) do |data, resp|
56
+ yield(data, resp) if block_given?
57
+ end
58
+ end
59
+
60
+ def transaction(transaction_id, params = {})
61
+ @client.transaction(self['id'], transaction_id, params) do |data, resp|
62
+ yield(data, resp) if block_given?
63
+ end
64
+ end
65
+
66
+ def send(params = {})
67
+ @client.send(self['id'], params) do |data, resp|
68
+ yield(data, resp) if block_given?
69
+ end
70
+ end
71
+
72
+ def transfer(params = {})
73
+ @client.transfer(self['id'], params) do |data, resp|
74
+ yield(data, resp) if block_given?
75
+ end
76
+ end
77
+
78
+ def request(params = {})
79
+ @client.request(self['id'], params) do |data, resp|
80
+ yield(data, resp) if block_given?
81
+ end
82
+ end
83
+
84
+ #
85
+ # Buys
86
+ #
87
+ def list_buys(params = {})
88
+ @client.list_buys(self['id'], params) do |data, resp|
89
+ yield(data, resp) if block_given?
90
+ end
91
+ end
92
+
93
+ def list_buy(transaction_id, params = {})
94
+ @client.list_buy(self['id'], transaction_id, params) do |data, resp|
95
+ yield(data, resp) if block_given?
96
+ end
97
+ end
98
+
99
+ def buy(params = {})
100
+ @client.buy(self['id'], params) do |data, resp|
101
+ yield(data, resp) if block_given?
102
+ end
103
+ end
104
+
105
+ def commit_buy(transaction_id, params = {})
106
+ @client.commit_buy(self['id'], transaction_id, params) do |data, resp|
107
+ yield(data, resp) if block_given?
108
+ end
109
+ end
110
+
111
+ #
112
+ # Sells
113
+ #
114
+ def list_sells(params = {})
115
+ @client.list_sells(self['id'], params) do |data, resp|
116
+ yield(data, resp) if block_given?
117
+ end
118
+ end
119
+
120
+ def list_sell(transaction_id, params = {})
121
+ @client.list_sell(self['id'], transaction_id, params) do |data, resp|
122
+ yield(data, resp) if block_given?
123
+ end
124
+ end
125
+
126
+ def sell(params = {})
127
+ @client.sell(self['id'], params) do |data, resp|
128
+ yield(data, resp) if block_given?
129
+ end
130
+ end
131
+
132
+ def commit_sell(transaction_id, params = {})
133
+ @client.commit_sell(self['id'], transaction_id, params) do |data, resp|
134
+ yield(data, resp) if block_given?
135
+ end
136
+ end
137
+
138
+ #
139
+ # Deposit
140
+ #
141
+ def list_deposits(params = {})
142
+ @client.list_deposits(self['id'], params) do |data, resp|
143
+ yield(data, resp) if block_given?
144
+ end
145
+ end
146
+
147
+ def list_deposit(transaction_id, params = {})
148
+ @client.list_deposit(self['id'], transaction_id, params) do |data, resp|
149
+ yield(data, resp) if block_given?
150
+ end
151
+ end
152
+
153
+ def deposit(params = {})
154
+ @client.deposit(self['id'], params) do |data, resp|
155
+ yield(data, resp) if block_given?
156
+ end
157
+ end
158
+
159
+ def commit_deposit(transaction_id, params = {})
160
+ @client.commit_deposit(self['id'], transaction_id, params) do |data, resp|
161
+ yield(data, resp) if block_given?
162
+ end
163
+ end
164
+
165
+ #
166
+ # Withdrawals
167
+ #
168
+ def list_withdrawals(params = {})
169
+ @client.list_withdrawals(self['id'], params) do |data, resp|
170
+ yield(data, resp) if block_given?
171
+ end
172
+ end
173
+
174
+ def list_withdrawal(transaction_id, params = {})
175
+ @client.list_withdrawal(self['id'], transaction_id, params) do |data, resp|
176
+ yield(data, resp) if block_given?
177
+ end
178
+ end
179
+
180
+ def withdraw(params = {})
181
+ @client.withdraw(self['id'], params) do |data, resp|
182
+ yield(data, resp) if block_given?
183
+ end
184
+ end
185
+
186
+ def commit_withdrawal(transaction_id, params = {})
187
+ @client.commit_withdrawal(self['id'], transaction_id, params) do |data, resp|
188
+ yield(data, resp) if block_given?
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,12 @@
1
+ module Coinbase
2
+ module Wallet
3
+ class Address < APIObject
4
+ def transactions(params = {})
5
+ @client.get("#{self['resource_path']}/transactions", params) do |resp|
6
+ out = resp.data.map { |item| Transaction.new(self, item) }
7
+ yield(out, resp) if block_given?
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,46 @@
1
+ module Coinbase
2
+ module Wallet
3
+ # Response item abstract model
4
+ class APIObject < Hash
5
+ def initialize(client, data)
6
+ super()
7
+ update(data)
8
+ @client = client
9
+ end
10
+
11
+ def refresh!(params = {})
12
+ @client.get(self['resource_path'], params) do |resp|
13
+ update(resp.data)
14
+ yield(resp.data, resp) if block_given?
15
+ end
16
+ end
17
+
18
+ def update(data)
19
+ return if data.nil?
20
+ data.each { |key, val| self[key] = val } if data.is_a?(Hash)
21
+ end
22
+
23
+ def format(key, val)
24
+ return if val.nil?
25
+ # Looks like a number or currency
26
+ if val.class == Hash
27
+ APIObject.new(@client, val)
28
+ elsif key =~ /_at$/ && (Time.iso8601(val) rescue nil)
29
+ Time.parse(val)
30
+ elsif key == "amount" && val =~ /^.{0,1}\s*[0-9,]*\.{0,1}[0-9]*$/
31
+ BigDecimal(val.gsub(/[^0-9\.-]/, ''))
32
+ else
33
+ val
34
+ end
35
+ end
36
+
37
+ def method_missing(method, *args, &blk)
38
+ format(method.to_s, self[method.to_s]) || super
39
+ end
40
+
41
+ def respond_to_missing?(method, include_all = false)
42
+ self.key?(method.to_s) || super
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,19 @@
1
+ module Coinbase
2
+ module Wallet
3
+ class Checkout < APIObject
4
+ def orders(params = {})
5
+ @client.get("#{self['resource_path']}/orders", params) do |resp|
6
+ out = resp.data.map { |item| Order.new(self, item) }
7
+ yield(out, resp) if block_given?
8
+ end
9
+ end
10
+
11
+ def create_order(params = {})
12
+ @client.post("#{self['resource_path']}/orders", params) do |resp|
13
+ out = Order.new(self, resp.data)
14
+ yield(out, resp) if block_given?
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ module Coinbase
2
+ module Wallet
3
+ class Order < APIObject
4
+ def refund!(params = {})
5
+ @client.post("#{self['resource_path']}/refund", params) do |resp|
6
+ update(resp.data)
7
+ yield(resp.data, resp) if block_given?
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,21 @@
1
+ module Coinbase
2
+ module Wallet
3
+ class Transaction < APIObject
4
+ end
5
+
6
+ class Request < Transaction
7
+ def resend!(params = {})
8
+ @client.post("#{self['resource_path']}/resend", params) do |resp|
9
+ yield(resp.data, resp) if block_given?
10
+ end
11
+ end
12
+
13
+ def cancel!(params = {})
14
+ @client.delete("#{self['resource_path']}", params) do |resp|
15
+ yield(resp.data, resp) if block_given?
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+