github.com/bitfinexcom/bitfinex-api-go@v0.0.0-20210608095005-9e0b26f200fb/v1/client.go (about) 1 // Package bitfinex is the official client to access to bitfinex.com API 2 package bitfinex 3 4 import ( 5 "crypto/hmac" 6 "crypto/sha512" 7 "encoding/base64" 8 "encoding/hex" 9 "encoding/json" 10 "fmt" 11 "io/ioutil" 12 "net/http" 13 "net/url" 14 15 "github.com/bitfinexcom/bitfinex-api-go/pkg/utils" 16 ) 17 18 const ( 19 // BaseURL is the v1 REST endpoint. 20 BaseURL = "https://api.bitfinex.com/v1/" 21 // WebSocketURL the v1 Websocket endpoint. 22 WebSocketURL = "wss://api-pub.bitfinex.com/ws/" 23 ) 24 25 // Client manages all the communication with the Bitfinex API. 26 type Client struct { 27 // Base URL for API requests. 28 BaseURL *url.URL 29 WebSocketURL string 30 WebSocketTLSSkipVerify bool 31 32 // Auth data 33 APIKey string 34 APISecret string 35 36 // Services 37 Pairs *PairsService 38 Stats *StatsService 39 Ticker *TickerService 40 Account *AccountService 41 Balances *BalancesService 42 Offers *OffersService 43 Credits *CreditsService 44 Deposit *DepositService 45 Lendbook *LendbookService 46 MarginInfo *MarginInfoService 47 MarginFunding *MarginFundingService 48 OrderBook *OrderBookService 49 Orders *OrderService 50 Trades *TradesService 51 Positions *PositionsService 52 History *HistoryService 53 WebSocket *WebSocketService 54 Wallet *WalletService 55 } 56 57 // NewClient creates new Bitfinex.com API client. 58 func NewClient() *Client { 59 baseURL, _ := url.Parse(BaseURL) 60 61 c := &Client{BaseURL: baseURL, WebSocketURL: WebSocketURL} 62 c.Pairs = &PairsService{client: c} 63 c.Stats = &StatsService{client: c} 64 c.Account = &AccountService{client: c} 65 c.Ticker = &TickerService{client: c} 66 c.Balances = &BalancesService{client: c} 67 c.Offers = &OffersService{client: c} 68 c.Credits = &CreditsService{client: c} 69 c.Deposit = &DepositService{client: c} 70 c.Lendbook = &LendbookService{client: c} 71 c.MarginInfo = &MarginInfoService{client: c} 72 c.MarginFunding = &MarginFundingService{client: c} 73 c.OrderBook = &OrderBookService{client: c} 74 c.Orders = &OrderService{client: c} 75 c.History = &HistoryService{client: c} 76 c.Trades = &TradesService{client: c} 77 c.Positions = &PositionsService{client: c} 78 c.Wallet = &WalletService{client: c} 79 c.WebSocket = NewWebSocketService(c) 80 c.WebSocketTLSSkipVerify = false 81 82 return c 83 } 84 85 // NewRequest create new API request. Relative url can be provided in refURL. 86 func (c *Client) newRequest(method string, refURL string, params url.Values) (*http.Request, error) { 87 rel, err := url.Parse(refURL) 88 if err != nil { 89 return nil, err 90 } 91 if params != nil { 92 rel.RawQuery = params.Encode() 93 } 94 var req *http.Request 95 u := c.BaseURL.ResolveReference(rel) 96 req, err = http.NewRequest(method, u.String(), nil) 97 98 if err != nil { 99 return nil, err 100 } 101 102 return req, nil 103 } 104 105 // newAuthenticatedRequest creates new http request for authenticated routes. 106 func (c *Client) newAuthenticatedRequest(m string, refURL string, data map[string]interface{}) (*http.Request, error) { 107 req, err := c.newRequest(m, refURL, nil) 108 if err != nil { 109 return nil, err 110 } 111 112 nonce := utils.GetNonce() 113 payload := map[string]interface{}{ 114 "request": "/v1/" + refURL, 115 "nonce": nonce, 116 } 117 118 for k, v := range data { 119 payload[k] = v 120 } 121 122 p, err := json.Marshal(payload) 123 if err != nil { 124 return nil, err 125 } 126 127 encoded := base64.StdEncoding.EncodeToString(p) 128 129 req.Header.Add("Content-Type", "application/json") 130 req.Header.Add("Accept", "application/json") 131 req.Header.Add("X-BFX-APIKEY", c.APIKey) 132 req.Header.Add("X-BFX-PAYLOAD", encoded) 133 sig, err := c.signPayload(encoded) 134 if err != nil { 135 return nil, err 136 } 137 req.Header.Add("X-BFX-SIGNATURE", sig) 138 139 return req, nil 140 } 141 142 func (c *Client) signPayload(payload string) (string, error) { 143 sig := hmac.New(sha512.New384, []byte(c.APISecret)) 144 _, err := sig.Write([]byte(payload)) 145 if err != nil { 146 return "", err 147 } 148 return hex.EncodeToString(sig.Sum(nil)), nil 149 } 150 151 // Auth sets api key and secret for usage is requests that requires authentication. 152 func (c *Client) Auth(key string, secret string) *Client { 153 c.APIKey = key 154 c.APISecret = secret 155 156 return c 157 } 158 159 var httpDo = func(req *http.Request) (*http.Response, error) { 160 return http.DefaultClient.Do(req) 161 } 162 163 // Do executes API request created by NewRequest method or custom *http.Request. 164 func (c *Client) do(req *http.Request, v interface{}) (*Response, error) { 165 resp, err := httpDo(req) 166 167 if err != nil { 168 return nil, err 169 } 170 defer resp.Body.Close() 171 172 response := newResponse(resp) 173 174 err = checkResponse(response) 175 if err != nil { 176 // Return response in case caller need to debug it. 177 return response, err 178 } 179 180 if v != nil { 181 err = json.Unmarshal(response.Body, v) 182 if err != nil { 183 return response, err 184 } 185 } 186 187 return response, nil 188 } 189 190 // Response is wrapper for standard http.Response and provides 191 // more methods. 192 type Response struct { 193 Response *http.Response 194 Body []byte 195 } 196 197 // newResponse creates new wrapper. 198 func newResponse(r *http.Response) *Response { 199 body, err := ioutil.ReadAll(r.Body) 200 if err != nil { 201 body = []byte(`Error reading body:` + err.Error()) 202 } 203 204 return &Response{r, body} 205 } 206 207 // String converts response body to string. 208 // An empty string will be returned if error. 209 func (r *Response) String() string { 210 return string(r.Body) 211 } 212 213 // ErrorResponse is the custom error type that is returned if the API returns an 214 // error. 215 type ErrorResponse struct { 216 Response *Response 217 Message string `json:"message"` 218 } 219 220 func (r *ErrorResponse) Error() string { 221 return fmt.Sprintf("%v %v: %d %v", 222 r.Response.Response.Request.Method, 223 r.Response.Response.Request.URL, 224 r.Response.Response.StatusCode, 225 r.Message, 226 ) 227 } 228 229 // checkResponse checks response status code and response 230 // for errors. 231 func checkResponse(r *Response) error { 232 if c := r.Response.StatusCode; 200 <= c && c <= 299 { 233 return nil 234 } 235 236 // Try to decode error message 237 errorResponse := &ErrorResponse{Response: r} 238 err := json.Unmarshal(r.Body, errorResponse) 239 if err != nil { 240 errorResponse.Message = "Error decoding response error message. " + 241 "Please see response body for more information." 242 } 243 244 return errorResponse 245 }