Merge pull request #2 from wuhewuhe/master
websocket public & private examples and impl
This commit is contained in:
commit
e557c96982
|
@ -31,15 +31,6 @@ type Client struct {
|
||||||
|
|
||||||
type doFunc func(req *http.Request) (*http.Response, error)
|
type doFunc func(req *http.Request) (*http.Response, error)
|
||||||
|
|
||||||
// Globals
|
|
||||||
const (
|
|
||||||
timestampKey = "X-BAPI-TIMESTAMP"
|
|
||||||
signatureKey = "X-BAPI-SIGN"
|
|
||||||
apiRequestKey = "X-BAPI-API-KEY"
|
|
||||||
recvWindowKey = "X-BAPI-RECV-WINDOW"
|
|
||||||
signTypeKey = "2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func currentTimestamp() int64 {
|
func currentTimestamp() int64 {
|
||||||
return FormatTimestamp(time.Now())
|
return FormatTimestamp(time.Now())
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
package bybit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MessageHandler func(message string) error
|
||||||
|
|
||||||
|
func (b *WebSocket) handleIncomingMessages() {
|
||||||
|
for {
|
||||||
|
_, message, err := b.conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error reading:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.onMessage != nil {
|
||||||
|
err := b.onMessage(string(message))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error handling message:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WebSocket) SetMessageHandler(handler MessageHandler) {
|
||||||
|
b.onMessage = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebSocket struct {
|
||||||
|
conn *websocket.Conn
|
||||||
|
url string
|
||||||
|
apiKey string
|
||||||
|
apiSecret string
|
||||||
|
onMessage MessageHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBybitPrivateWebSocket(url, apiKey, apiSecret string, handler MessageHandler) *WebSocket {
|
||||||
|
return &WebSocket{
|
||||||
|
url: url,
|
||||||
|
apiKey: apiKey,
|
||||||
|
apiSecret: apiSecret,
|
||||||
|
onMessage: handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBybitPublicWebSocket(url string, handler MessageHandler) *WebSocket {
|
||||||
|
return &WebSocket{
|
||||||
|
url: url,
|
||||||
|
onMessage: handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WebSocket) Connect(args []string) error {
|
||||||
|
var err error
|
||||||
|
b.conn, _, err = websocket.DefaultDialer.Dial(b.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.requiresAuthentication() {
|
||||||
|
if err = b.sendAuth(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go b.handleIncomingMessages()
|
||||||
|
|
||||||
|
Ping(b)
|
||||||
|
|
||||||
|
return b.sendSubscription(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ping(b *WebSocket) {
|
||||||
|
ticker := time.NewTicker(15 * time.Second)
|
||||||
|
go func() {
|
||||||
|
defer ticker.Stop() // Ensure the ticker is stopped when this goroutine ends
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C: // Wait until the ticker sends a signal
|
||||||
|
if err := b.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
|
fmt.Println("Failed to send ping:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WebSocket) Disconnect() error {
|
||||||
|
return b.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WebSocket) Send(message string) error {
|
||||||
|
return b.conn.WriteMessage(websocket.TextMessage, []byte(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WebSocket) requiresAuthentication() bool {
|
||||||
|
return b.url == WEBSOCKET_PRIVATE_MAINNET ||
|
||||||
|
b.url == WEBSOCKET_PRIVATE_TESTNET ||
|
||||||
|
b.url == V3_CONTRACT_PRIVATE ||
|
||||||
|
b.url == V3_UNIFIED_PRIVATE ||
|
||||||
|
b.url == V3_SPOT_PRIVATE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WebSocket) sendAuth() error {
|
||||||
|
// Get current Unix time in milliseconds
|
||||||
|
expires := time.Now().UnixNano()/1e6 + 10000
|
||||||
|
val := fmt.Sprintf("GET/realtime%d", expires)
|
||||||
|
|
||||||
|
h := hmac.New(sha256.New, []byte(b.apiSecret))
|
||||||
|
h.Write([]byte(val))
|
||||||
|
|
||||||
|
// Convert to hexadecimal instead of base64
|
||||||
|
signature := hex.EncodeToString(h.Sum(nil))
|
||||||
|
fmt.Println("signature generated : " + signature)
|
||||||
|
|
||||||
|
authMessage := map[string]interface{}{
|
||||||
|
"req_id": uuid.New(), // You would need to implement or find a package for generating GUID
|
||||||
|
"op": "auth",
|
||||||
|
"args": []interface{}{b.apiKey, expires, signature},
|
||||||
|
}
|
||||||
|
fmt.Println("auth args:", fmt.Sprintf("%v", authMessage["args"]))
|
||||||
|
return b.SendAsJson(authMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WebSocket) sendSubscription(args []string) error {
|
||||||
|
subMessage := map[string]interface{}{
|
||||||
|
"req_id": uuid.New(),
|
||||||
|
"op": "subscribe",
|
||||||
|
"args": args,
|
||||||
|
}
|
||||||
|
fmt.Println("subscribe msg:", fmt.Sprintf("%v", subMessage["args"]))
|
||||||
|
return b.SendAsJson(subMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WebSocket) SendAsJson(v interface{}) error {
|
||||||
|
data, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.Send(string(data))
|
||||||
|
}
|
32
consts.go
32
consts.go
|
@ -1,5 +1,33 @@
|
||||||
package bybit
|
package bybit
|
||||||
|
|
||||||
const Name = "bybit.api.go"
|
const (
|
||||||
|
Name = "bybit.api.go"
|
||||||
|
Version = "1.0.0"
|
||||||
|
// WebSocket public channel - Mainnet
|
||||||
|
SPOT_MAINNET = "wss://stream.bybit.com/v5/public/spot"
|
||||||
|
LINEAR_MAINNET = "wss://stream.bybit.com/v5/public/linear"
|
||||||
|
INVERSE_MAINNET = "wss://stream.bybit.com/v5/public/inverse"
|
||||||
|
OPTION_MAINNET = "wss://stream.bybit.com/v5/public/option"
|
||||||
|
|
||||||
const Version = "1.0.0"
|
// WebSocket public channel - Testnet
|
||||||
|
SPOT_TESTNET = "wss://stream-testnet.bybit.com/v5/public/spot"
|
||||||
|
LINEAR_TESTNET = "wss://stream-testnet.bybit.com/v5/public/linear"
|
||||||
|
INVERSE_TESTNET = "wss://stream-testnet.bybit.com/v5/public/inverse"
|
||||||
|
OPTION_TESTNET = "wss://stream-testnet.bybit.com/v5/public/option"
|
||||||
|
|
||||||
|
// WebSocket private channel
|
||||||
|
WEBSOCKET_PRIVATE_MAINNET = "wss://stream.bybit.com/v5/private"
|
||||||
|
WEBSOCKET_PRIVATE_TESTNET = "wss://stream-testnet.bybit.com/v5/private"
|
||||||
|
|
||||||
|
// V3
|
||||||
|
V3_CONTRACT_PRIVATE = "wss://stream.bybit.com/contract/private/v3"
|
||||||
|
V3_UNIFIED_PRIVATE = "wss://stream.bybit.com/unified/private/v3"
|
||||||
|
V3_SPOT_PRIVATE = "wss://stream.bybit.com/spot/private/v3"
|
||||||
|
|
||||||
|
// Globals
|
||||||
|
timestampKey = "X-BAPI-TIMESTAMP"
|
||||||
|
signatureKey = "X-BAPI-SIGN"
|
||||||
|
apiRequestKey = "X-BAPI-API-KEY"
|
||||||
|
recvWindowKey = "X-BAPI-RECV-WINDOW"
|
||||||
|
signTypeKey = "2"
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
bybit "bybit.go.api"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ws := bybit.NewBybitPrivateWebSocket("wss://stream-testnet.bybit.com/v5/private", "8wYkmpLsMg10eNQyPm", "Ouxc34myDnXvei54XsBZgoQzfGxO4bkr2Zsj", func(message string) error {
|
||||||
|
fmt.Println("Received:", message)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
// Connect and subscribe to the desired topic
|
||||||
|
_ = ws.Connect([]string{"order"})
|
||||||
|
select {}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
bybit "bybit.go.api"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create a public WebSocket client
|
||||||
|
ws := bybit.NewBybitPublicWebSocket("wss://stream.bybit.com/v5/public/spot", func(message string) error {
|
||||||
|
fmt.Println("Received:", message)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
// Connect and subscribe to the desired topic
|
||||||
|
_ = ws.Connect([]string{"orderbook.1.BTCUSDT"})
|
||||||
|
select {}
|
||||||
|
}
|
3
go.mod
3
go.mod
|
@ -4,5 +4,6 @@ go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bitly/go-simplejson v0.5.1
|
github.com/bitly/go-simplejson v0.5.1
|
||||||
|
github.com/google/uuid v1.4.0
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
)
|
)
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -1,3 +1,7 @@
|
||||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||||
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
|
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||||
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
|
Loading…
Reference in New Issue