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  }