Skip to content

Maintenance #111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 55 additions & 15 deletions v2/rest/client.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package rest

import (
"crypto/hmac"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/bitfinexcom/bitfinex-api-go/utils"
"io"
"io/ioutil"
"net/http"
Expand All @@ -11,6 +15,11 @@ import (

var productionBaseURL = "https://api.bitfinex.com/v2/"

type requestFactory interface {
NewAuthenticatedRequestWithData(refURL string, data map[string]interface{}) (Request, error)
NewAuthenticatedRequest(refURL string) (Request, error)
}

type Synchronous interface {
Request(request Request) ([]interface{}, error)
}
Expand All @@ -19,6 +28,7 @@ type Client struct {
// base members for synchronous API
apiKey string
apiSecret string
nonce utils.NonceGenerator

// service providers
Orders OrderService
Expand Down Expand Up @@ -48,7 +58,7 @@ func NewClientWithURLHttpDo(base string, httpDo func(c *http.Client, r *http.Req
httpDo: httpDo,
HTTPClient: http.DefaultClient,
}
return NewClientWithSynchronous(sync)
return NewClientWithSynchronousNonce(sync, utils.NewEpochNonceGenerator()) // make nonce easier to inject?
}

func NewClientWithURL(url string) *Client {
Expand All @@ -58,31 +68,33 @@ func NewClientWithURL(url string) *Client {
return NewClientWithURLHttpDo(url, httpDo)
}

// mock me
func NewClientWithSynchronous(sync Synchronous) *Client {
// mock me in tests
func NewClientWithSynchronousNonce(sync Synchronous, nonce utils.NonceGenerator) *Client {
c := &Client{
Synchronous: sync,
nonce: nonce,
}
c.Orders = OrderService{Synchronous: c}
c.Orders = OrderService{Synchronous: c, requestFactory: c}
c.Book = BookService{Synchronous: c}
c.Trades = TradeService{Synchronous: c}
c.Trades = TradeService{Synchronous: c, requestFactory: c}
c.Platform = PlatformService{Synchronous: c}
c.Positions = PositionService{Synchronous: c}
c.Positions = PositionService{Synchronous: c, requestFactory: c}
return c
}

func (c Client) Credentials(key string, secret string) *Client {
func (c *Client) Credentials(key string, secret string) *Client {
c.apiKey = key
c.apiSecret = secret
return &c
return c
}

// Request is a wrapper for standard http.Request. Default method is POST with no data.
type Request struct {
RefURL string // ref url
Data map[string]interface{} // body data
Method string // http method
Params url.Values // query parameters
RefURL string // ref url
Data map[string]interface{} // body data
Method string // http method
Params url.Values // query parameters
Headers map[string]string
}

// Response is a wrapper for standard http.Response and provides more methods.
Expand All @@ -91,6 +103,33 @@ type Response struct {
Body []byte
}

func (c *Client) sign(msg string) string {
sig := hmac.New(sha512.New384, []byte(c.apiSecret))
sig.Write([]byte(msg))
return hex.EncodeToString(sig.Sum(nil))
}

func (c *Client) NewAuthenticatedRequest(refURL string) (Request, error) {
return c.NewAuthenticatedRequestWithData(refURL, nil)
}

func (c *Client) NewAuthenticatedRequestWithData(refURL string, data map[string]interface{}) (Request, error) {
authURL := "auth/r/" + refURL
req := NewRequestWithData(authURL, data)
nonce := c.nonce.GetNonce()
b, err := json.Marshal(data)
if err != nil {
return Request{}, err
}
msg := "/api/v2/" + authURL + nonce + string(b)
req.Headers["Content-Type"] = "applicaiton/json"
req.Headers["Accept"] = "application/json"
req.Headers["bfx-nonce"] = nonce
req.Headers["bfx-signature"] = c.sign(msg)
req.Headers["bfx-apikey"] = c.apiKey
return req, nil
}

func NewRequest(refURL string) Request {
return NewRequestWithDataMethod(refURL, nil, "POST")
}
Expand All @@ -105,9 +144,10 @@ func NewRequestWithData(refURL string, data map[string]interface{}) Request {

func NewRequestWithDataMethod(refURL string, data map[string]interface{}, method string) Request {
return Request{
RefURL: refURL,
Data: data,
Method: method,
RefURL: refURL,
Data: data,
Method: method,
Headers: make(map[string]string),
}
}

Expand Down
16 changes: 11 additions & 5 deletions v2/rest/orders.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ import (

// OrderService manages data flow for the Order API endpoint
type OrderService struct {
requestFactory
Synchronous
}

// All returns all orders for the authenticated account.
func (s *OrderService) All(symbol string) (*bitfinex.OrderSnapshot, error) {
raw, err := s.Request(NewRequest(path.Join("orders", symbol)))

req, err := s.requestFactory.NewAuthenticatedRequest(path.Join("orders", symbol))
if err != nil {
return nil, err
}
raw, err := s.Request(req)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -54,9 +58,11 @@ func (s *OrderService) History(symbol string) (*bitfinex.OrderSnapshot, error) {
if symbol == "" {
return nil, fmt.Errorf("symbol cannot be empty")
}

raw, err := s.Request(NewRequest(path.Join("orders", symbol, "hist")))

req, err := s.requestFactory.NewAuthenticatedRequest(path.Join("orders", symbol, "hist"))
if err != nil {
return nil, err
}
raw, err := s.Request(req)
if err != nil {
return nil, err
}
Expand Down
16 changes: 8 additions & 8 deletions v2/rest/platform_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ func (p *PlatformService) Status() (bool, error) {
if err != nil {
return false, err
}
/*
// raw is an interface type, but we only care about len & index 0
s := make([]int, len(raw))
for i, v := range raw {
s[i] = v.(int)
}
*/
return len(raw) > 0 && raw[0].(int) == 1, nil
/*
// raw is an interface type, but we only care about len & index 0
s := make([]int, len(raw))
for i, v := range raw {
s[i] = v.(int)
}
*/
return len(raw) > 0 && raw[0].(float64) == 1, nil
}
7 changes: 6 additions & 1 deletion v2/rest/positions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import (

// PositionService manages the Position endpoint.
type PositionService struct {
requestFactory
Synchronous
}

// All returns all positions for the authenticated account.
func (s *PositionService) All() (*bitfinex.PositionSnapshot, error) {
raw, err := s.Request(NewRequest("positions"))
req, err := s.requestFactory.NewAuthenticatedRequest("positions")
if err != nil {
return nil, err
}
raw, err := s.Request(req)

if err != nil {
return nil, err
Expand Down
8 changes: 6 additions & 2 deletions v2/rest/trades.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ import (

// TradeService manages the Trade endpoint.
type TradeService struct {
requestFactory
Synchronous
}

// All returns all orders for the authenticated account.
func (s *TradeService) All(symbol string) (*bitfinex.TradeSnapshot, error) {

raw, err := s.Request(NewRequestWithData(path.Join("trades", symbol, "hist"), map[string]interface{}{"start": nil, "end": nil, "limit": nil}))
req, err := s.requestFactory.NewAuthenticatedRequestWithData(path.Join("trades", symbol, "hist"), map[string]interface{}{"start": nil, "end": nil, "limit": nil})
if err != nil {
return nil, err
}
raw, err := s.Request(req)

if err != nil {
return nil, err
Expand Down
3 changes: 0 additions & 3 deletions v2/websocket/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ func (w *ws) Connect() error {
log.Printf("connecting ws to %s", w.BaseURL)
ws, resp, err := d.Dial(w.BaseURL, nil)
if err != nil {
close(w.downstream) // signal to parent connection failure thru listen channel
if err == websocket.ErrBadHandshake {
log.Printf("bad handshake: status code %d", resp.StatusCode)
}
Expand All @@ -76,7 +75,6 @@ func (w *ws) Send(ctx context.Context, msg interface{}) error {
if err != nil {
return err
}
log.Printf("ws->srv: %s", string(bs))

select {
case <-ctx.Done():
Expand Down Expand Up @@ -128,7 +126,6 @@ func (w *ws) listenWs() {
w.cleanup(err)
return
}
log.Printf("srv->ws: %s", string(msg))
w.downstream <- msg
}
}
Expand Down