-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathgobcy.go
179 lines (169 loc) · 4.73 KB
/
gobcy.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//Package gobcy implements a wrapper for the http://www.blockcypher.com API.
//You can use it to interact with addresses, transactions, and blocks from
//various blockchains, including Bitcoin's main and test3 chains,
//and the BlockCypher test chain.
//
//Please note: we assume you use are using a 64-bit architecture for deployment,
//which automatically makes `int` types 64-bit. Without 64-bit ints, some values
//might overflow on certain calls, depending on the blockchain you are querying.
//If you are using a 32-bit system, you can change all `int` types to `int64` to
//explicitly work around this issue.
package gobcy
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"strconv"
)
const baseURL = "https://api.blockcypher.com/v1/"
//API stores your BlockCypher Token, and the coin/chain
//you're querying. Coins can be "btc","bcy","ltc", and "doge".
//Chains can be "main", "test3", or "test", depending on the Coin.
//Check http://dev.blockcypher.com/ for more information.
//All your credentials are stored within an API struct, as are
//many of the API methods.
//You can allocate an API struct like so:
// bc = gobcy.API{"your-api-token","btc","main"}
//Then query as you like:
// chain = bc.GetChain()
type API struct {
Token, Coin, Chain string
}
//getResponse is a boilerplate for HTTP GET responses.
func getResponse(target *url.URL, decTarget interface{}) (err error) {
resp, err := http.Get(target.String())
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err = respErrorMaker(resp.StatusCode, resp.Body)
return
}
dec := json.NewDecoder(resp.Body)
err = dec.Decode(decTarget)
return
}
//postResponse is a boilerplate for HTTP POST responses.
func postResponse(target *url.URL, encTarget interface{}, decTarget interface{}) (err error) {
var data bytes.Buffer
enc := json.NewEncoder(&data)
if err = enc.Encode(encTarget); err != nil {
return
}
resp, err := http.Post(target.String(), "application/json", &data)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
err = respErrorMaker(resp.StatusCode, resp.Body)
return
}
dec := json.NewDecoder(resp.Body)
err = dec.Decode(decTarget)
return
}
//putResponse is a boilerplate for HTTP PUT responses.
func putResponse(target *url.URL, encTarget interface{}) (err error) {
var data bytes.Buffer
enc := json.NewEncoder(&data)
if err = enc.Encode(encTarget); err != nil {
return
}
req, err := http.NewRequest("PUT", target.String(), &data)
if err != nil {
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
err = respErrorMaker(resp.StatusCode, resp.Body)
}
return
}
//deleteResponse is a boilerplate for HTTP DELETE responses.
func deleteResponse(target *url.URL) (err error) {
req, err := http.NewRequest("DELETE", target.String(), nil)
if err != nil {
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
err = respErrorMaker(resp.StatusCode, resp.Body)
}
return
}
//respErrorMaker checks error messages/if they are multiple errors
//serializes them into a single error message
func respErrorMaker(statusCode int, body io.Reader) (err error) {
status := "HTTP " + strconv.Itoa(statusCode) + " " + http.StatusText(statusCode)
if statusCode == 429 {
err = errors.New(status)
return
}
type errorJSON struct {
Err string `json:"error"`
Errors []struct {
Err string `json:"error"`
} `json:"errors"`
}
var msg errorJSON
dec := json.NewDecoder(body)
err = dec.Decode(&msg)
if err != nil {
return err
}
var errtxt string
errtxt += msg.Err
for i, v := range msg.Errors {
if i == len(msg.Errors)-1 {
errtxt += v.Err
} else {
errtxt += v.Err + ", "
}
}
if errtxt == "" {
err = errors.New(status)
} else {
err = errors.New(status + ", Message(s): " + errtxt)
}
return
}
//constructs BlockCypher URLs with parameters for requests
func (api *API) buildURL(u string, params map[string]string) (target *url.URL, err error) {
target, err = url.Parse(baseURL + api.Coin + "/" + api.Chain + u)
if err != nil {
return
}
values := target.Query()
//Set parameters
for k, v := range params {
values.Set(k, v)
}
//add token to url, if present
if api.Token != "" {
values.Set("token", api.Token)
}
target.RawQuery = values.Encode()
return
}
// CheckUsage checks token usage
func (api *API) CheckUsage() (usage TokenUsage, err error) {
u, err := url.Parse(baseURL + "tokens/" + api.Token)
if err != nil {
return
}
err = getResponse(u, &usage)
return
}