pkg/exchange: add login method

This commit is contained in:
Edwin 2023-11-10 21:56:18 +08:00
parent 8d4213794b
commit 6c96d12d99
6 changed files with 53 additions and 11 deletions

View File

@ -76,7 +76,7 @@ func (c *RestClient) NewAuthenticatedRequest(ctx context.Context, method, refURL
} }
// See https://bitgetlimited.github.io/apidoc/en/spot/#signature // See https://bitgetlimited.github.io/apidoc/en/spot/#signature
// sign( // Sign(
// timestamp + // timestamp +
// method.toUpperCase() + // method.toUpperCase() +
// requestPath + "?" + queryString + // requestPath + "?" + queryString +
@ -94,7 +94,7 @@ func (c *RestClient) NewAuthenticatedRequest(ctx context.Context, method, refURL
} }
signKey := timestamp + strings.ToUpper(method) + path + string(body) 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)) req, err := http.NewRequestWithContext(ctx, method, pathURL.String(), bytes.NewReader(body))
if err != nil { if err != nil {
@ -110,7 +110,7 @@ func (c *RestClient) NewAuthenticatedRequest(ctx context.Context, method, refURL
return req, nil 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)) var sig = hmac.New(sha256.New, []byte(secret))
_, err := sig.Write([]byte(payload)) _, err := sig.Write([]byte(payload))
if err != nil { if err != nil {

View File

@ -6,6 +6,10 @@ import (
"github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi" "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi"
) )
const (
PrivateWebSocketURL = "wss://ws.bitget.com/v2/ws/private"
)
type APIResponse = bitgetapi.APIResponse type APIResponse = bitgetapi.APIResponse
type Client struct { type Client struct {

View File

@ -83,8 +83,7 @@ func (e *Exchange) PlatformFeeCurrency() string {
} }
func (e *Exchange) NewStream() types.Stream { func (e *Exchange) NewStream() types.Stream {
// TODO implement me return NewStream(e.key, e.secret, e.passphrase)
panic("implement me")
} }
func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) { func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {

View File

@ -5,10 +5,14 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/gorilla/websocket" "strconv"
"strings" "strings"
"time"
"github.com/gorilla/websocket"
"github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi" "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi"
v2 "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi/v2"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
@ -21,6 +25,7 @@ var (
type Stream struct { type Stream struct {
types.StandardStream types.StandardStream
key, secret, passphrase string
bookEventCallbacks []func(o BookEvent) bookEventCallbacks []func(o BookEvent)
marketTradeEventCallbacks []func(o MarketTradeEvent) marketTradeEventCallbacks []func(o MarketTradeEvent)
KLineEventCallbacks []func(o KLineEvent) KLineEventCallbacks []func(o KLineEvent)
@ -28,10 +33,13 @@ type Stream struct {
lastCandle map[string]types.KLine lastCandle map[string]types.KLine
} }
func NewStream() *Stream { func NewStream(key, secret, passphrase string) *Stream {
stream := &Stream{ stream := &Stream{
StandardStream: types.NewStandardStream(), StandardStream: types.NewStandardStream(),
lastCandle: map[string]types.KLine{}, lastCandle: map[string]types.KLine{},
key: key,
secret: secret,
passphrase: passphrase,
} }
stream.SetEndpointCreator(stream.createEndpoint) stream.SetEndpointCreator(stream.createEndpoint)
@ -89,7 +97,7 @@ func (s *Stream) createEndpoint(_ context.Context) (string, error) {
if s.PublicOnly { if s.PublicOnly {
url = bitgetapi.PublicWebSocketURL url = bitgetapi.PublicWebSocketURL
} else { } else {
url = bitgetapi.PrivateWebSocketURL url = v2.PrivateWebSocketURL
} }
return url, nil return url, nil
} }
@ -123,7 +131,22 @@ func (s *Stream) handlerConnect() {
// errors are handled in the syncSubscriptions, so they are skipped here. // errors are handled in the syncSubscriptions, so they are skipped here.
_ = s.syncSubscriptions(WsEventSubscribe) _ = s.syncSubscriptions(WsEventSubscribe)
} else { } 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
}
} }
} }

View File

@ -19,7 +19,9 @@ func getTestClientOrSkip(t *testing.T) *Stream {
t.Skip("skip test for CI") 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) { func TestStream(t *testing.T) {
@ -122,6 +124,14 @@ func TestStream(t *testing.T) {
<-c <-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) { func TestStream_parseWebSocketEvent(t *testing.T) {

View File

@ -34,6 +34,11 @@ type WsArg struct {
Channel ChannelType `json:"channel"` Channel ChannelType `json:"channel"`
// InstId Instrument ID. e.q. BTCUSDT, ETHUSDT // InstId Instrument ID. e.q. BTCUSDT, ETHUSDT
InstId string `json:"instId"` InstId string `json:"instId"`
ApiKey string `json:"apiKey"`
Passphrase string `json:"passphrase"`
Timestamp string `json:"timestamp"`
Sign string `json:"sign"`
} }
type WsEventType string type WsEventType string
@ -41,6 +46,7 @@ type WsEventType string
const ( const (
WsEventSubscribe WsEventType = "subscribe" WsEventSubscribe WsEventType = "subscribe"
WsEventUnsubscribe WsEventType = "unsubscribe" WsEventUnsubscribe WsEventType = "unsubscribe"
WsEventLogin WsEventType = "login"
WsEventError WsEventType = "error" WsEventError WsEventType = "error"
) )
@ -76,7 +82,7 @@ func (w *WsEvent) IsValid() error {
case WsEventError: case WsEventError:
return fmt.Errorf("websocket request error, op: %s, code: %d, msg: %s", w.Op, w.Code, w.Msg) 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 // Actually, this code is unnecessary because the events are either `Subscribe` or `Unsubscribe`, But to avoid bugs
// in the exchange, we still check. // in the exchange, we still check.
if w.Code != 0 || len(w.Msg) != 0 { if w.Code != 0 || len(w.Msg) != 0 {