Skip to content

Commit ddcbadb

Browse files
authored
Merge pull request #2289 from c9s/kbearXD/binance/margin-trading-listenToken
FEATURE: [binance] listen token support for margin trading
2 parents 8a11c87 + 53c6cf8 commit ddcbadb

File tree

6 files changed

+514
-30
lines changed

6 files changed

+514
-30
lines changed

pkg/exchange/binance/binanceapi/create_margin_account_listen_token_request_requestgen.go

Lines changed: 230 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package binanceapi
2+
3+
import (
4+
"github.com/c9s/bbgo/pkg/types"
5+
"github.com/c9s/requestgen"
6+
)
7+
8+
// CreateMarginAccountListenTokenResponse represents the response from creating a margin account listen token
9+
type CreateMarginAccountListenTokenResponse struct {
10+
Token string `json:"token"`
11+
ExpirationTime types.MillisecondTimestamp `json:"expirationTime"`
12+
}
13+
14+
//go:generate requestgen -method POST -url "/sapi/v1/userListenToken" -type CreateMarginAccountListenTokenRequest -responseType .CreateMarginAccountListenTokenResponse
15+
type CreateMarginAccountListenTokenRequest struct {
16+
client requestgen.AuthenticatedAPIClient
17+
18+
// Trading pair symbol; required when isIsolated is true, e.g., BNBUSDT
19+
symbol *string `param:"symbol"`
20+
21+
// Whether it is isolated margin; true means isolated; default is cross margin
22+
isIsolated *bool `param:"isIsolated"`
23+
24+
// Validity in milliseconds; default 24 hours, maximum 24 hours
25+
validity *int64 `param:"validity"`
26+
}
27+
28+
func (c *RestClient) NewCreateMarginAccountListenTokenRequest() *CreateMarginAccountListenTokenRequest {
29+
return &CreateMarginAccountListenTokenRequest{client: c}
30+
}

pkg/exchange/binance/exchange.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ const TestNetWebSocketURL = "wss://testnet.binance.vision"
5050
const TestNetFuturesBaseURL = "https://testnet.binancefuture.com"
5151

5252
// New WebSocket API Endpoints
53-
// listenKey will be deprecated. Official recommendation is to use ws-api for user data stream
53+
// DEPRECATED (2025-10-06): listenKey via wss://stream.binance.com:9443 will be removed
54+
// Official recommendation is to use ws-api for user data stream or new listenToken subscription method
5455
const WsSpotWebSocketURL = "wss://ws-api.binance.com:443/ws-api/v3"
5556
const WsTestNetWebSocketURL = "wss://testnet.binance.vision/ws-api/v3"
5657

@@ -120,6 +121,11 @@ type Exchange struct {
120121

121122
ed25519authentication
122123

124+
// useListenKey enables the deprecated listenKey method via wss://stream.binance.com:9443
125+
// This method is deprecated as of 2025-10-06 and will be removed by Binance in the future
126+
// Default behavior (false) uses the new recommended listenToken method
127+
useListenKey bool
128+
123129
// client is used for spot & margin
124130
client *binance.Client
125131

@@ -240,6 +246,13 @@ func (e *Exchange) setServerTimeOffset(ctx context.Context) {
240246
}
241247
}
242248

249+
// EnableListenKey enables the deprecated listenKey method via wss://stream.binance.com:9443
250+
// This method is deprecated as of 2025-10-06 and will be removed by Binance in the future
251+
// By default, the new listenToken method is used
252+
func (e *Exchange) EnableListenKey() {
253+
e.useListenKey = true
254+
}
255+
243256
func (e *Exchange) Name() types.ExchangeName {
244257
return types.ExchangeBinance
245258
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package binance
2+
3+
import (
4+
"context"
5+
"testing"
6+
)
7+
8+
func TestExchange_EnableListenKey(t *testing.T) {
9+
exchange := &Exchange{}
10+
11+
// Initially should be false (default uses new method)
12+
if exchange.useListenKey {
13+
t.Error("UseListenKey should be false by default")
14+
}
15+
16+
// After enabling, should be true (uses deprecated method)
17+
exchange.EnableListenKey()
18+
if !exchange.useListenKey {
19+
t.Error("UseListenKey should be true after EnableListenKey()")
20+
}
21+
}
22+
23+
func TestStream_ListenKeyConfiguration(t *testing.T) {
24+
// Test default behavior (new listenToken method)
25+
exchange1 := &Exchange{}
26+
stream1 := NewStream(exchange1, nil, nil)
27+
if stream1.exchange.useListenKey {
28+
t.Error("Stream should use listenToken method by default (useListenKey should be false)")
29+
}
30+
31+
// Test enabling deprecated listenKey method
32+
exchange2 := &Exchange{}
33+
exchange2.EnableListenKey()
34+
stream2 := NewStream(exchange2, nil, nil)
35+
if !stream2.exchange.useListenKey {
36+
t.Error("Stream should have useListenKey enabled when exchange has UseListenKey enabled")
37+
}
38+
}
39+
40+
func TestStream_FetchListenToken_ListenKeyEnabled(t *testing.T) {
41+
stream := &Stream{
42+
exchange: &Exchange{
43+
useListenKey: true,
44+
},
45+
}
46+
47+
// Should return error when deprecated listenKey method is enabled
48+
_, _, err := stream.fetchListenToken(context.Background())
49+
if err == nil {
50+
t.Error("Expected error when listenKey method is enabled")
51+
}
52+
}
53+
54+
func TestStream_FetchListenToken_NewMethodDefault(t *testing.T) {
55+
stream := &Stream{
56+
exchange: &Exchange{
57+
useListenKey: false,
58+
},
59+
}
60+
61+
// This would normally require a real API client, so we just test the logic check
62+
if stream.exchange.useListenKey {
63+
t.Error("useListenKey should be false for new method")
64+
}
65+
}

0 commit comments

Comments
 (0)