From 6c96d12d99e77178f83e5fd7bc233c57b0007f27 Mon Sep 17 00:00:00 2001 From: Edwin Date: Fri, 10 Nov 2023 21:56:18 +0800 Subject: [PATCH] pkg/exchange: add login method --- pkg/exchange/bitget/bitgetapi/client.go | 6 ++--- pkg/exchange/bitget/bitgetapi/v2/client.go | 4 +++ pkg/exchange/bitget/exchange.go | 3 +-- pkg/exchange/bitget/stream.go | 31 +++++++++++++++++++--- pkg/exchange/bitget/stream_test.go | 12 ++++++++- pkg/exchange/bitget/types.go | 8 +++++- 6 files changed, 53 insertions(+), 11 deletions(-) diff --git a/pkg/exchange/bitget/bitgetapi/client.go b/pkg/exchange/bitget/bitgetapi/client.go index 19b145d5d..823f3a7c0 100644 --- a/pkg/exchange/bitget/bitgetapi/client.go +++ b/pkg/exchange/bitget/bitgetapi/client.go @@ -76,7 +76,7 @@ func (c *RestClient) NewAuthenticatedRequest(ctx context.Context, method, refURL } // See https://bitgetlimited.github.io/apidoc/en/spot/#signature - // sign( + // Sign( // timestamp + // method.toUpperCase() + // requestPath + "?" + queryString + @@ -94,7 +94,7 @@ func (c *RestClient) NewAuthenticatedRequest(ctx context.Context, method, refURL } signKey := timestamp + strings.ToUpper(method) + path + string(body) - signature := sign(signKey, c.secret) + signature := Sign(signKey, c.secret) req, err := http.NewRequestWithContext(ctx, method, pathURL.String(), bytes.NewReader(body)) if err != nil { @@ -110,7 +110,7 @@ func (c *RestClient) NewAuthenticatedRequest(ctx context.Context, method, refURL return req, nil } -func sign(payload string, secret string) string { +func Sign(payload string, secret string) string { var sig = hmac.New(sha256.New, []byte(secret)) _, err := sig.Write([]byte(payload)) if err != nil { diff --git a/pkg/exchange/bitget/bitgetapi/v2/client.go b/pkg/exchange/bitget/bitgetapi/v2/client.go index 3a2b2204d..d15cd889b 100644 --- a/pkg/exchange/bitget/bitgetapi/v2/client.go +++ b/pkg/exchange/bitget/bitgetapi/v2/client.go @@ -6,6 +6,10 @@ import ( "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi" ) +const ( + PrivateWebSocketURL = "wss://ws.bitget.com/v2/ws/private" +) + type APIResponse = bitgetapi.APIResponse type Client struct { diff --git a/pkg/exchange/bitget/exchange.go b/pkg/exchange/bitget/exchange.go index 05a10bb26..988b626d2 100644 --- a/pkg/exchange/bitget/exchange.go +++ b/pkg/exchange/bitget/exchange.go @@ -83,8 +83,7 @@ func (e *Exchange) PlatformFeeCurrency() string { } func (e *Exchange) NewStream() types.Stream { - // TODO implement me - panic("implement me") + return NewStream(e.key, e.secret, e.passphrase) } func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) { diff --git a/pkg/exchange/bitget/stream.go b/pkg/exchange/bitget/stream.go index 039c65127..4636be4db 100644 --- a/pkg/exchange/bitget/stream.go +++ b/pkg/exchange/bitget/stream.go @@ -5,10 +5,14 @@ import ( "context" "encoding/json" "fmt" - "github.com/gorilla/websocket" + "strconv" "strings" + "time" + + "github.com/gorilla/websocket" "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi" + v2 "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi/v2" "github.com/c9s/bbgo/pkg/types" ) @@ -21,6 +25,7 @@ var ( type Stream struct { types.StandardStream + key, secret, passphrase string bookEventCallbacks []func(o BookEvent) marketTradeEventCallbacks []func(o MarketTradeEvent) KLineEventCallbacks []func(o KLineEvent) @@ -28,10 +33,13 @@ type Stream struct { lastCandle map[string]types.KLine } -func NewStream() *Stream { +func NewStream(key, secret, passphrase string) *Stream { stream := &Stream{ StandardStream: types.NewStandardStream(), lastCandle: map[string]types.KLine{}, + key: key, + secret: secret, + passphrase: passphrase, } stream.SetEndpointCreator(stream.createEndpoint) @@ -89,7 +97,7 @@ func (s *Stream) createEndpoint(_ context.Context) (string, error) { if s.PublicOnly { url = bitgetapi.PublicWebSocketURL } else { - url = bitgetapi.PrivateWebSocketURL + url = v2.PrivateWebSocketURL } return url, nil } @@ -123,7 +131,22 @@ func (s *Stream) handlerConnect() { // errors are handled in the syncSubscriptions, so they are skipped here. _ = s.syncSubscriptions(WsEventSubscribe) } else { - log.Error("*** PRIVATE API NOT IMPLEMENTED ***") + timestamp := strconv.FormatInt(time.Now().Unix(), 10) + + if err := s.Conn.WriteJSON(WsOp{ + Op: WsEventLogin, + Args: []WsArg{ + { + ApiKey: s.key, + Passphrase: s.passphrase, + Timestamp: timestamp, + Sign: bitgetapi.Sign(fmt.Sprintf("%sGET/user/verify", timestamp), s.secret), + }, + }, + }); err != nil { + log.WithError(err).Error("failed to auth request") + return + } } } diff --git a/pkg/exchange/bitget/stream_test.go b/pkg/exchange/bitget/stream_test.go index b33e6afa2..d05cbd037 100644 --- a/pkg/exchange/bitget/stream_test.go +++ b/pkg/exchange/bitget/stream_test.go @@ -19,7 +19,9 @@ func getTestClientOrSkip(t *testing.T) *Stream { t.Skip("skip test for CI") } - return NewStream() + return NewStream(os.Getenv("BITGET_API_KEY"), + os.Getenv("BITGET_API_SECRET"), + os.Getenv("BITGET_API_PASSPHRASE")) } func TestStream(t *testing.T) { @@ -122,6 +124,14 @@ func TestStream(t *testing.T) { <-c }) + t.Run("private test", func(t *testing.T) { + err := s.Connect(context.Background()) + assert.NoError(t, err) + + c := make(chan struct{}) + <-c + }) + } func TestStream_parseWebSocketEvent(t *testing.T) { diff --git a/pkg/exchange/bitget/types.go b/pkg/exchange/bitget/types.go index a1107cad6..f6fbfa06c 100644 --- a/pkg/exchange/bitget/types.go +++ b/pkg/exchange/bitget/types.go @@ -34,6 +34,11 @@ type WsArg struct { Channel ChannelType `json:"channel"` // InstId Instrument ID. e.q. BTCUSDT, ETHUSDT InstId string `json:"instId"` + + ApiKey string `json:"apiKey"` + Passphrase string `json:"passphrase"` + Timestamp string `json:"timestamp"` + Sign string `json:"sign"` } type WsEventType string @@ -41,6 +46,7 @@ type WsEventType string const ( WsEventSubscribe WsEventType = "subscribe" WsEventUnsubscribe WsEventType = "unsubscribe" + WsEventLogin WsEventType = "login" WsEventError WsEventType = "error" ) @@ -76,7 +82,7 @@ func (w *WsEvent) IsValid() error { case WsEventError: return fmt.Errorf("websocket request error, op: %s, code: %d, msg: %s", w.Op, w.Code, w.Msg) - case WsEventSubscribe, WsEventUnsubscribe: + case WsEventSubscribe, WsEventUnsubscribe, WsEventLogin: // Actually, this code is unnecessary because the events are either `Subscribe` or `Unsubscribe`, But to avoid bugs // in the exchange, we still check. if w.Code != 0 || len(w.Msg) != 0 {