-
Notifications
You must be signed in to change notification settings - Fork 16
/
client.go
163 lines (145 loc) · 4.07 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
83
84
85
86
87
88
89
90
91
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*
* Coinbase Golang API Library
*
* Copyright (C) 2017 Lukas Matt <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
Package coinbase provides a simple user interface for API calls to coinbase.com.
Example:
c := coinbase.APIClient{
Key: "123",
Secret: "123456",
}
acc, err := c.Accounts()
if err != nil {
fmt.Println(err)
return
}
for i, acc := range accounts.Data {
fmt.Printf("ID: %s\nName: %s\nType: %s\nAmount: %f\nCurrency: %s\n",
acc.Id, acc.Name, acc.Type,
acc.Balance.Amount, acc.Balance.Currency)
}
# sample output
ID: 1234-12-1234-1232
Name: Test Wallet
Type: BTC
Amount: 0.0
Currency: EUR
[...]
*/
package coinbase
import (
"bytes"
"fmt"
"encoding/json"
"net/http"
"crypto/sha256"
"crypto/hmac"
"strconv"
"io"
)
const (
// ENDPOINT defaults to https://api.coinbase.com
// but can be overridden for test purposes
ENDPOINT = "https://api.coinbase.com"
// API_VERSION since version two you have to
// specify a API version in your http request headers
API_VERSION = "2016-03-08"
)
// APIClient is the interface for most of the API calls
// If Endpoint or ApiVersion aren't defined the library
// will use the default https://api.coinbase.com
type APIClient struct {
Key string
Secret string
Endpoint string
ApiVersion string
}
// APIClientEpoch is used for decoding json "/v2/time" responses
type APIClientEpoch struct {
Data struct {
Epoch int64
}
}
// Fetch works as a wrapper for all kind of http requests. It requires a http method
// and a relative path to the API endpoint. It will try to decode all results into
// a single interface type which you can provide.
func (a *APIClient) Fetch(method, path string, body interface{}, result interface{}) error {
if a.Endpoint == "" {
// use default endpoint
a.Endpoint = ENDPOINT
}
if a.ApiVersion == "" {
// use default api version
a.ApiVersion = API_VERSION
}
client := &http.Client{}
var bodyBuffered io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return err
}
bodyBuffered = bytes.NewBuffer([]byte(data))
}
req, err := http.NewRequest(method, a.Endpoint + path, bodyBuffered)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("CB-VERSION", a.ApiVersion)
// do not authenticate on public time api call
if path[len(path)-4:] != "time" {
err = a.Authenticate(path, req, body)
if err != nil {
return err
}
}
resp, err := client.Do(req)
if err != nil {
return err
}
err = json.NewDecoder(resp.Body).Decode(result)
if err != nil {
return err
}
return nil
}
// Authenticate works with the Fetch call and adds certain Headers
// to the http request. This includes the actual API key and the
// timestamp of the request. Also a signature which is encoded
// with hmac and the API secret key.
func (a *APIClient) Authenticate(path string, req *http.Request, body interface{}) error {
time, err := a.GetCurrentTime()
if err != nil {
return err
}
timestamp := strconv.FormatInt(time.Data.Epoch, 10)
message := timestamp + req.Method + path
if body != nil {
bodyBytes, _ := json.Marshal(body)
message += string(bodyBytes)
}
sha := sha256.New
h := hmac.New(sha, []byte(a.Secret))
h.Write([]byte(message))
signature := fmt.Sprintf("%x", h.Sum(nil))
req.Header.Set("CB-ACCESS-KEY", a.Key)
req.Header.Set("CB-ACCESS-SIGN", signature)
req.Header.Set("CB-ACCESS-TIMESTAMP", timestamp)
return nil
}