coinbase 0.0.1 → 4.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +7 -0
- data/CONTRIBUTING.md +53 -0
- data/Gemfile +4 -0
- data/LICENSE +201 -0
- data/README.md +621 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/coinbase.gemspec +26 -0
- data/lib/coinbase/util.rb +16 -0
- data/lib/coinbase/wallet/adapters/em_http.rb +78 -0
- data/lib/coinbase/wallet/adapters/net_http.rb +68 -0
- data/lib/coinbase/wallet/api_client.rb +755 -0
- data/lib/coinbase/wallet/api_errors.rb +120 -0
- data/lib/coinbase/wallet/api_response.rb +41 -0
- data/lib/coinbase/wallet/ca-coinbase.crt +101 -0
- data/lib/coinbase/wallet/client.rb +101 -0
- data/lib/coinbase/wallet/coinbase-callback.pub +14 -0
- data/lib/coinbase/wallet/models/account.rb +193 -0
- data/lib/coinbase/wallet/models/address.rb +12 -0
- data/lib/coinbase/wallet/models/api_object.rb +46 -0
- data/lib/coinbase/wallet/models/checkout.rb +19 -0
- data/lib/coinbase/wallet/models/order.rb +12 -0
- data/lib/coinbase/wallet/models/transaction.rb +21 -0
- data/lib/coinbase/wallet/models/transfer.rb +13 -0
- data/lib/coinbase/wallet/models/user.rb +15 -0
- data/lib/coinbase/wallet/version.rb +5 -0
- data/lib/coinbase/wallet.rb +24 -156
- data/spec/account_spec.rb +199 -0
- data/spec/callback_signature_verification_spec.rb +16 -0
- data/spec/clients/client_spec.rb +34 -0
- data/spec/clients/oauth_client_spec.rb +56 -0
- data/spec/endpoints_spec.rb +352 -0
- data/spec/error_spec.rb +137 -0
- data/spec/models/address_spec.rb +26 -0
- data/spec/models/api_object_spec.rb +70 -0
- data/spec/models/checkout_spec.rb +52 -0
- data/spec/models/current_user_spec.rb +29 -0
- data/spec/models/order_spec.rb +52 -0
- data/spec/models/request_spec.rb +47 -0
- data/spec/models/transfer_spec.rb +64 -0
- data/spec/models/user_spec.rb +24 -0
- data/spec/spec_helper.rb +13 -0
- metadata +67 -112
- 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.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,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
|
+
|