Skip to content
This repository has been archived by the owner on Jul 4, 2024. It is now read-only.

Commit

Permalink
update readme etc
Browse files Browse the repository at this point in the history
  • Loading branch information
larscom committed Dec 2, 2023
1 parent 8830957 commit c34e1cb
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 48 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

> Go **thread safe** client library for Bitvavo v2 (https://docs.bitvavo.com)
Go Bitvavo is a **thread safe** client written in GO to interact with the Bitvavo platform. For _now_, only websockets (read only) are supported, so you can listen to all events that occur on the bitvavo platform (e.g: candles, ticker, orders, fills, etc)
Go Bitvavo is a **thread safe** client written in GO to interact with the Bitvavo platform. For _now_, mostly websockets (read only) are supported, so you can listen to all events that occur on the bitvavo platform (e.g: candles, ticker, orders, fills, etc)

The HTTP client (read / write) is limited at the moment, it'll will grow with more functionality over time.

## 📒 Features

- [x] WebSockets
-- Read only
- [ ] REST (_soon_)
-- Read / Write
- [ ] ...
- [x] WebSocket Client -- Read only (100%)
- [ ] Http Client (_soon_) -- Read / Write
- Not complete yet, will grow with more functionality over time.

## 🚀 Installation

Expand Down Expand Up @@ -349,13 +349,13 @@ type FillEvent struct {
You can enable debug logging by providing an option to the Websocket constructor

```go
ws, err := bitvavo.NewWsClient(bitvavo.WithDebug(true))
ws, err := bitvavo.NewWsClient(wsc.WithDebug(true))
```

### Auto Reconnect

You can disable auto reconnecting to the websocket by providing an option to the Websocket constructor

```go
ws, err := bitvavo.NewWsClient(bitvavo.WithAutoReconnect(false))
ws, err := bitvavo.NewWsClient(wsc.WithAutoReconnect(false))
```
6 changes: 5 additions & 1 deletion examples/http/account/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func main() {
key = os.Getenv("API_KEY")
secret = os.Getenv("API_SECRET")
client = bitvavo.NewHttpClient(httpc.WithDebug(true))
authClient = client.ToAuthClient(key, secret, 10000)
authClient = client.ToAuthClient(key, secret, 0)
)

balance, err := authClient.GetBalance()
Expand All @@ -31,4 +31,8 @@ func main() {
log.Fatal(err)
}
log.Println("Account", account)

ratelimit := client.GetRateLimit()
resetAt := client.GetRateLimitResetAt()
log.Println("RateLimit", ratelimit, "ResetAt", resetAt)
}
8 changes: 5 additions & 3 deletions examples/http/time/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (

func main() {
client := bitvavo.NewHttpClient()
v, _ := client.GetTime()
log.Println("Time", v)

time, err := client.GetTime()
if err != nil {
log.Fatal(err)
}
log.Println("Time", time)
}
41 changes: 31 additions & 10 deletions httpc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,57 @@ import (
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"

"github.com/goccy/go-json"
"github.com/larscom/go-bitvavo/v2/crypto"
"github.com/larscom/go-bitvavo/v2/log"
"github.com/larscom/go-bitvavo/v2/util"
)

var (
client = http.DefaultClient
)

func httpGet[T any](url string, updateRateLimit func(int), config *authConfig) (T, error) {
func httpGet[T any](
url string,
updateRateLimit func(int64),
updateRateLimitResetAt func(time.Time),
config *authConfig,
) (T, error) {
req, _ := http.NewRequest("GET", url, nil)
return httpDo[T](req, updateRateLimit, config)
return httpDo[T](req, updateRateLimit, updateRateLimitResetAt, config)
}

func httpPost[T any](url string, body T, updateRateLimit func(int), config *authConfig) (T, error) {
func httpPost[T any](
url string,
body T,
updateRateLimit func(int64),
updateRateLimitResetAt func(time.Time),
config *authConfig,
) (T, error) {
payload, err := json.Marshal(body)
if err != nil {
return body, err
}
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
return httpDo[T](req, updateRateLimit, config)
return httpDo[T](req, updateRateLimit, updateRateLimitResetAt, config)
}

func httpDo[T any](request *http.Request, updateRatelimit func(int), config *authConfig) (T, error) {
func httpDo[T any](
request *http.Request,
updateRatelimit func(int64),
updateRateLimitResetAt func(time.Time),
config *authConfig,
) (T, error) {
log.Logger().Debug("executing request", "method", request.Method, "url", request.URL.String())

var data T
if err := applyHeaders(request, config); err != nil {
return data, err
}

log.Logger().Debug("executing request", "method", request.Method, "url", request.URL.String())

response, err := client.Do(request)
if err != nil {
return data, err
Expand All @@ -50,8 +66,13 @@ func httpDo[T any](request *http.Request, updateRatelimit func(int), config *aut
if len(value) == 0 {
return data, fmt.Errorf("header: %s didn't contain a value", headerRatelimit)
}
ratelimit, _ := strconv.Atoi(value[0])
updateRatelimit(ratelimit)
updateRatelimit(util.MustInt64(value[0]))
}
if key == headerRatelimitResetAt {
if len(value) == 0 {
return data, fmt.Errorf("header: %s didn't contain a value", headerRatelimitResetAt)
}
updateRateLimitResetAt(time.UnixMilli(util.MustInt64(value[0])))
}
}

Expand Down
69 changes: 51 additions & 18 deletions httpc/httpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,61 @@ package httpc
import (
"fmt"
"sync"
"time"
)

const (
httpUrl = "https://api.bitvavo.com/v2"

headerRatelimit = "Bitvavo-Ratelimit-Remaining"
headerAccessKey = "Bitvavo-Access-Key"
headerAccessSignature = "Bitvavo-Access-Signature"
headerAccessTimestamp = "Bitvavo-Access-Timestamp"
headerAccessWindow = "Bitvavo-Access-Window"
httpUrl = "https://api.bitvavo.com/v2"
maxWindowTimeMs = 60000

headerRatelimit = "Bitvavo-Ratelimit-Remaining"
headerRatelimitResetAt = "Bitvavo-Ratelimit-Resetat"
headerAccessKey = "Bitvavo-Access-Key"
headerAccessSignature = "Bitvavo-Access-Signature"
headerAccessTimestamp = "Bitvavo-Access-Timestamp"
headerAccessWindow = "Bitvavo-Access-Window"
)

const DefaultWindowTimeMs = 10000

type HttpClient interface {
// ToAuthClient returns a client for authenticated requests
// ToAuthClient returns a client for authenticated requests.
// You need to provide an apiKey and an apiSecret which you can create in the bitvavo dashboard.
//
// WindowTimeMs is the window that allows execution of your request.
//
// If you set the value to 0, the default value of 10000 will be set.
// Whenever you go higher than the max value of 60000 the value will be set to 60000.
ToAuthClient(apiKey string, apiSecret string, windowTimeMs uint64) HttpClientAuth

// GetTime returns the current server time in milliseconds since 1 Jan 1970
GetTime() (int64, error)

// GetRateLimit returns the remaining rate limit.
// Default value: -1
GetRateLimit() int
GetRateLimit() int64

// GetRateLimitResetTime() time.Time
// GetRateLimitResetAt returns the time (local time) when the counter resets.
// Default value: time.Now()
GetRateLimitResetAt() time.Time
}

type Option func(*httpClient)

type httpClient struct {
mu sync.RWMutex
ratelimit int
debug bool
debug bool

mu sync.RWMutex
ratelimit int64
ratelimitResetAt time.Time

authClient *httpClientAuth
}

func NewHttpClient(options ...Option) HttpClient {
client := &httpClient{
ratelimit: -1,
ratelimit: -1,
ratelimitResetAt: time.Now(),
}

for _, opt := range options {
Expand All @@ -63,34 +79,51 @@ func (c *httpClient) ToAuthClient(apiKey string, apiSecret string, windowTimeMs
if c.hasAuthClient() {
return c.authClient
}

if windowTimeMs == 0 {
windowTimeMs = DefaultWindowTimeMs
}
if windowTimeMs > maxWindowTimeMs {
windowTimeMs = maxWindowTimeMs
}
config := &authConfig{
windowTimeMs: windowTimeMs,
apiKey: apiKey,
apiSecret: apiSecret,
}
c.authClient = newHttpClientAuth(c.updateRateLimit, config)
c.authClient = newHttpClientAuth(c.updateRateLimit, c.updateRateLimitResetAt, config)
return c.authClient
}

func (c *httpClient) GetRateLimit() int {
func (c *httpClient) GetRateLimit() int64 {
return c.ratelimit
}

func (c *httpClient) GetRateLimitResetAt() time.Time {
return c.ratelimitResetAt
}

func (c *httpClient) GetTime() (int64, error) {
resp, err := httpGet[map[string]float64](fmt.Sprintf("%s/time", httpUrl), c.updateRateLimit, nil)
resp, err := httpGet[map[string]float64](fmt.Sprintf("%s/time", httpUrl), c.updateRateLimit, c.updateRateLimitResetAt, nil)
if err != nil {
return 0, err
}

return int64(resp["time"]), nil
}

func (c *httpClient) updateRateLimit(ratelimit int) {
func (c *httpClient) updateRateLimit(ratelimit int64) {
c.mu.Lock()
defer c.mu.Unlock()
c.ratelimit = ratelimit
}

func (c *httpClient) updateRateLimitResetAt(resetAt time.Time) {
c.mu.Lock()
defer c.mu.Unlock()
c.ratelimitResetAt = resetAt
}

func (c *httpClient) hasAuthClient() bool {
return c.authClient != nil
}
21 changes: 14 additions & 7 deletions httpc/httpclientauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package httpc

import (
"fmt"
"time"

"github.com/larscom/go-bitvavo/v2/jsond"
)
Expand All @@ -15,8 +16,9 @@ type HttpClientAuth interface {
}

type httpClientAuth struct {
config *authConfig
updateRateLimit func(int)
config *authConfig
updateRateLimit func(int64)
updateRateLimitResetAt func(time.Time)
}

type authConfig struct {
Expand All @@ -25,17 +27,22 @@ type authConfig struct {
windowTimeMs uint64
}

func newHttpClientAuth(updateRateLimit func(int), config *authConfig) *httpClientAuth {
func newHttpClientAuth(
updateRateLimit func(int64),
updateRateLimitResetAt func(time.Time),
config *authConfig,
) *httpClientAuth {
return &httpClientAuth{
updateRateLimit: updateRateLimit,
config: config,
updateRateLimit: updateRateLimit,
updateRateLimitResetAt: updateRateLimitResetAt,
config: config,
}
}

func (c *httpClientAuth) GetBalance() ([]jsond.Balance, error) {
return httpGet[[]jsond.Balance](fmt.Sprintf("%s/balance", httpUrl), c.updateRateLimit, c.config)
return httpGet[[]jsond.Balance](fmt.Sprintf("%s/balance", httpUrl), c.updateRateLimit, c.updateRateLimitResetAt, c.config)
}

func (c *httpClientAuth) GetAccount() (jsond.Account, error) {
return httpGet[jsond.Account](fmt.Sprintf("%s/account", httpUrl), c.updateRateLimit, c.config)
return httpGet[jsond.Account](fmt.Sprintf("%s/account", httpUrl), c.updateRateLimit, c.updateRateLimitResetAt, c.config)
}
1 change: 0 additions & 1 deletion wsc/wsclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const (
wsUrl = "wss://ws.bitvavo.com/v2"
readLimit = 655350
handshakeTimeout = 45 * time.Second
maxWindowTimeMs = 60000
)

type EventHandler[T any] interface {
Expand Down

0 comments on commit c34e1cb

Please sign in to comment.