diff --git a/htx/spot/rest/basic.go b/htx/spot/rest/basic.go new file mode 100644 index 0000000..bec7f6e --- /dev/null +++ b/htx/spot/rest/basic.go @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, LinstoHu + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rest + +import ( + "context" + "errors" + "net/http" + + "github.com/linstohu/nexapi/htx/spot/rest/types" + "github.com/linstohu/nexapi/htx/utils" +) + +func (scli *SpotClient) GetSymbols(ctx context.Context, param types.GetSymbolsParam) (*types.GetSymbolsResp, error) { + req := utils.HTTPRequest{ + BaseURL: scli.cli.GetBaseURL(), + Path: "/v2/settings/common/symbols", + Method: http.MethodGet, + Query: param, + } + + { + headers, err := scli.cli.GetHeaders() + if err != nil { + return nil, err + } + req.Headers = headers + } + + resp, err := scli.cli.SendHTTPRequest(ctx, req) + if err != nil { + return nil, err + } + + var ret types.GetSymbolsResp + if err := resp.ReadJsonBody(&ret); err != nil { + return nil, errors.New(resp.Error()) + } + + return &ret, nil +} diff --git a/htx/spot/rest/client.go b/htx/spot/rest/client.go index 944811a..6706a48 100644 --- a/htx/spot/rest/client.go +++ b/htx/spot/rest/client.go @@ -41,9 +41,9 @@ type SpotClientCfg struct { Logger *slog.Logger BaseURL string `validate:"required"` - Key string `validate:"required"` - Secret string `validate:"required"` - SignVersion string `validate:"required"` + Key string + Secret string + SignVersion string } func NewSpotClient(cfg *SpotClientCfg) (*SpotClient, error) { @@ -60,7 +60,7 @@ func NewSpotClient(cfg *SpotClientCfg) (*SpotClient, error) { BaseURL: cfg.BaseURL, Key: cfg.Key, Secret: cfg.Secret, - SignVersion: cfg.SignVersion, + SignVersion: utils.ApiKeyVersionV2, }) if err != nil { return nil, err @@ -73,6 +73,10 @@ func NewSpotClient(cfg *SpotClientCfg) (*SpotClient, error) { } func (scli *SpotClient) GetAccountInfo(ctx context.Context) (*types.GetAccountInfoResponse, error) { + if err := scli.cli.CheckAuth(); err != nil { + return nil, err + } + req := utils.HTTPRequest{ BaseURL: scli.cli.GetBaseURL(), Path: "/v1/account/accounts", @@ -88,12 +92,67 @@ func (scli *SpotClient) GetAccountInfo(ctx context.Context) (*types.GetAccountIn } { - values, err := scli.cli.GenSignatureValues(req) + query := scli.cli.GenAuthParams() + + signStr, err := scli.cli.NormalizeRequestContent(req, query) + if err != nil { + return nil, err + } + + h := scli.cli.Sign([]byte(signStr)) + if err != nil { + return nil, err + } + + query.Signature = h + + req.Query = query + } + + resp, err := scli.cli.SendHTTPRequest(ctx, req) + if err != nil { + return nil, err + } + + var ret types.GetAccountInfoResponse + if err := resp.ReadJsonBody(&ret); err != nil { + return nil, errors.New(resp.Error()) + } + + return &ret, nil +} + +func (scli *SpotClient) GetAccountValuation(ctx context.Context, param types.GetAccountValuationParam) (*types.GetAccountValuationResp, error) { + if err := scli.cli.CheckAuth(); err != nil { + return nil, err + } + + err := scli.validate.Struct(param) + if err != nil { + return nil, err + } + + req := utils.HTTPRequest{ + BaseURL: scli.cli.GetBaseURL(), + Path: "/v2/account/valuation", + Method: http.MethodGet, + } + + { + headers, err := scli.cli.GetHeaders() if err != nil { return nil, err } + req.Headers = headers + } + + { + query := types.GetAccountValuationParams{ + GetAccountValuationParam: param, + DefaultAuthParam: scli.cli.GenAuthParams(), + } - signStr, err := scli.cli.NormalizeRequestContent(req, values) + signStr, err := scli.cli.NormalizeRequestContent(req, query) if err != nil { return nil, err } @@ -103,8 +162,9 @@ func (scli *SpotClient) GetAccountInfo(ctx context.Context) (*types.GetAccountIn return nil, err } - values.Add("Signature", h) - req.Query = values + query.DefaultAuthParam.Signature = h + + req.Query = query } resp, err := scli.cli.SendHTTPRequest(ctx, req) @@ -112,7 +172,7 @@ func (scli *SpotClient) GetAccountInfo(ctx context.Context) (*types.GetAccountIn return nil, err } - var ret types.GetAccountInfoResponse + var ret types.GetAccountValuationResp if err := resp.ReadJsonBody(&ret); err != nil { return nil, errors.New(resp.Error()) } diff --git a/htx/spot/rest/client_test.go b/htx/spot/rest/client_test.go index 42ba6e7..9b7d90c 100644 --- a/htx/spot/rest/client_test.go +++ b/htx/spot/rest/client_test.go @@ -22,6 +22,7 @@ import ( "os" "testing" + "github.com/linstohu/nexapi/htx/spot/rest/types" "github.com/linstohu/nexapi/htx/utils" "github.com/stretchr/testify/assert" ) @@ -42,6 +43,24 @@ func testNewSpotClient(t *testing.T) *SpotClient { return cli } +func TestGetSymbols(t *testing.T) { + cli := testNewSpotClient(t) + + _, err := cli.GetSymbols(context.TODO(), types.GetSymbolsParam{}) + + assert.Nil(t, err) +} + +func TestGetMergedMarketTicker(t *testing.T) { + cli := testNewSpotClient(t) + + _, err := cli.GetMergedMarketTicker(context.TODO(), types.GetMergedMarketTickerParam{ + Symbol: "btcusdt", + }) + + assert.Nil(t, err) +} + func TestGetAccountInfo(t *testing.T) { cli := testNewSpotClient(t) @@ -49,3 +68,27 @@ func TestGetAccountInfo(t *testing.T) { assert.Nil(t, err) } + +func TestGetAccountValuation(t *testing.T) { + cli := testNewSpotClient(t) + + _, err := cli.GetAccountValuation(context.TODO(), + types.GetAccountValuationParam{}, + ) + + assert.Nil(t, err) +} + +func TestNewOrder(t *testing.T) { + cli := testNewSpotClient(t) + + _, err := cli.NewOrder(context.TODO(), types.NewOrderParam{ + AccountID: "", + Symbol: "usdcusdt", + Type: "buy-limit", + Amount: "12", + Price: "0.9", + }) + + assert.Nil(t, err) +} diff --git a/htx/spot/rest/market.go b/htx/spot/rest/market.go new file mode 100644 index 0000000..bffa028 --- /dev/null +++ b/htx/spot/rest/market.go @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, LinstoHu + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rest + +import ( + "context" + "errors" + "net/http" + + "github.com/linstohu/nexapi/htx/spot/rest/types" + "github.com/linstohu/nexapi/htx/utils" +) + +func (scli *SpotClient) GetMergedMarketTicker(ctx context.Context, param types.GetMergedMarketTickerParam) (*types.GetMergedMarketTickerResp, error) { + err := scli.validate.Struct(param) + if err != nil { + return nil, err + } + + req := utils.HTTPRequest{ + BaseURL: scli.cli.GetBaseURL(), + Path: "/market/detail/merged", + Method: http.MethodGet, + Query: param, + } + + { + headers, err := scli.cli.GetHeaders() + if err != nil { + return nil, err + } + req.Headers = headers + } + + resp, err := scli.cli.SendHTTPRequest(ctx, req) + if err != nil { + return nil, err + } + + var ret types.GetMergedMarketTickerResp + if err := resp.ReadJsonBody(&ret); err != nil { + return nil, errors.New(resp.Error()) + } + + return &ret, nil +} diff --git a/htx/spot/rest/trading.go b/htx/spot/rest/trading.go new file mode 100644 index 0000000..ef0c59c --- /dev/null +++ b/htx/spot/rest/trading.go @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023, LinstoHu + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rest + +import ( + "context" + "errors" + "net/http" + + "github.com/linstohu/nexapi/htx/spot/rest/types" + "github.com/linstohu/nexapi/htx/utils" +) + +func (scli *SpotClient) NewOrder(ctx context.Context, param types.NewOrderParam) (*types.NewOrderResp, error) { + if err := scli.cli.CheckAuth(); err != nil { + return nil, err + } + + err := scli.validate.Struct(param) + if err != nil { + return nil, err + } + + req := utils.HTTPRequest{ + BaseURL: scli.cli.GetBaseURL(), + Path: "/v1/order/orders/place", + Method: http.MethodPost, + Body: param, + } + + { + headers, err := scli.cli.GetHeaders() + if err != nil { + return nil, err + } + req.Headers = headers + } + + { + query := scli.cli.GenAuthParams() + + signStr, err := scli.cli.NormalizeRequestContent(req, query) + if err != nil { + return nil, err + } + + h := scli.cli.Sign([]byte(signStr)) + if err != nil { + return nil, err + } + + query.Signature = h + + req.Query = query + } + + resp, err := scli.cli.SendHTTPRequest(ctx, req) + if err != nil { + return nil, err + } + + var ret types.NewOrderResp + if err := resp.ReadJsonBody(&ret); err != nil { + return nil, errors.New(resp.Error()) + } + + return &ret, nil +} diff --git a/htx/spot/rest/types/account.go b/htx/spot/rest/types/account.go index 5e6d2fd..a155830 100644 --- a/htx/spot/rest/types/account.go +++ b/htx/spot/rest/types/account.go @@ -17,6 +17,10 @@ package types +import ( + htxutils "github.com/linstohu/nexapi/htx/utils" +) + type GetAccountInfoResponse struct { Status string `json:"status"` Data []AccountInfo `json:"data"` @@ -27,3 +31,38 @@ type AccountInfo struct { Subtype string `json:"subtype"` State string `json:"state"` } + +type GetAccountValuationParam struct { + AccountType string `url:"accountType,omitempty" validate:"omitempty"` + ValuationCurrency string `url:"valuationCurrency,omitempty" validate:"omitempty"` +} + +type GetAccountValuationParams struct { + GetAccountValuationParam + htxutils.DefaultAuthParam +} + +type GetAccountValuationResp struct { + htxutils.V2Response + Data AccountValue `json:"data"` +} + +type Updated struct { + Success bool `json:"success,omitempty"` + Time int64 `json:"time,omitempty"` +} + +type ProfitAccountBalanceList struct { + DistributionType string `json:"distributionType,omitempty"` + Balance float64 `json:"balance,omitempty"` + Success bool `json:"success,omitempty"` + AccountBalance string `json:"accountBalance,omitempty"` +} + +type AccountValue struct { + Updated Updated `json:"updated,omitempty"` + TodayProfitRate string `json:"todayProfitRate,omitempty"` + TotalBalance string `json:"totalBalance,omitempty"` + TodayProfit string `json:"todayProfit,omitempty"` + ProfitAccountBalanceList []ProfitAccountBalanceList `json:"profitAccountBalanceList,omitempty"` +} diff --git a/htx/spot/rest/types/basic.go b/htx/spot/rest/types/basic.go new file mode 100644 index 0000000..bc00298 --- /dev/null +++ b/htx/spot/rest/types/basic.go @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, LinstoHu + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package types + +import ( + htxutils "github.com/linstohu/nexapi/htx/utils" +) + +type GetSymbolsParam struct { + Ts int64 `url:"ts,omitempty" validate:"omitempty"` +} + +type GetSymbolsResp struct { + htxutils.V1Response + Ts string `json:"ts"` + Data []Symbol `json:"data,omitempty"` +} + +type Symbol struct { + Sc string `json:"sc,omitempty"` + Dn string `json:"dn,omitempty"` + Bc string `json:"bc,omitempty"` + Bcdn string `json:"bcdn,omitempty"` + Qc string `json:"qc,omitempty"` + Qcdn string `json:"qcdn,omitempty"` + State string `json:"state,omitempty"` + Whe bool `json:"whe,omitempty"` + Cd bool `json:"cd,omitempty"` + Te bool `json:"te,omitempty"` + Toa int64 `json:"toa,omitempty"` + Sp string `json:"sp,omitempty"` + W int `json:"w,omitempty"` + Tpp int `json:"tpp,omitempty"` + Tap int `json:"tap,omitempty"` + Ttp int `json:"ttp,omitempty"` + Fp int `json:"fp,omitempty"` + SuspendDesc string `json:"suspend_desc,omitempty"` + Tags string `json:"tags,omitempty"` + Lr any `json:"lr,omitempty"` + Smlr any `json:"smlr,omitempty"` + Flr any `json:"flr,omitempty"` + Wr string `json:"wr,omitempty"` + D any `json:"d,omitempty"` + Elr any `json:"elr,omitempty"` + P any `json:"p,omitempty"` +} diff --git a/htx/spot/rest/types/market.go b/htx/spot/rest/types/market.go new file mode 100644 index 0000000..7796fc3 --- /dev/null +++ b/htx/spot/rest/types/market.go @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023, LinstoHu + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package types + +import ( + htxutils "github.com/linstohu/nexapi/htx/utils" +) + +type GetMergedMarketTickerParam struct { + Symbol string `url:"symbol" validate:"required"` +} + +type GetMergedMarketTickerResp struct { + htxutils.V1Response + Ts int64 `json:"ts"` + Tick Tick `json:"tick,omitempty"` +} + +type Tick struct { + ID int64 `json:"id,omitempty"` + Version int64 `json:"version,omitempty"` + Open float64 `json:"open,omitempty"` + Close float64 `json:"close,omitempty"` + Low float64 `json:"low,omitempty"` + High float64 `json:"high,omitempty"` + Amount float64 `json:"amount,omitempty"` + Vol float64 `json:"vol,omitempty"` + Count int `json:"count,omitempty"` + Bid []float64 `json:"bid,omitempty"` + Ask []float64 `json:"ask,omitempty"` +} diff --git a/htx/spot/rest/types/trading.go b/htx/spot/rest/types/trading.go new file mode 100644 index 0000000..8d66f3f --- /dev/null +++ b/htx/spot/rest/types/trading.go @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023, LinstoHu + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package types + +import ( + htxutils "github.com/linstohu/nexapi/htx/utils" +) + +type NewOrderParam struct { + AccountID string `json:"account-id" validate:"required"` + Symbol string `json:"symbol" validate:"required"` + Type string `json:"type" validate:"required"` + Amount string `json:"amount" validate:"required"` + Price string `json:"price,omitempty" validate:"omitempty"` + Source string `json:"source,omitempty" validate:"omitempty"` + ClientOrderID string `json:"client-order-id,omitempty" validate:"omitempty"` + SelfMatchPrevent int `json:"self-match-prevent,omitempty" validate:"omitempty"` + StopPrice string `json:"stop-price,omitempty" validate:"omitempty"` + Operator string `json:"operator,omitempty" validate:"omitempty"` +} + +type NewOrderResp struct { + htxutils.V1Response + Data string `json:"data,omitempty"` +} diff --git a/htx/utils/client.go b/htx/utils/client.go index f159450..b73d6cb 100644 --- a/htx/utils/client.go +++ b/htx/utils/client.go @@ -24,6 +24,7 @@ import ( "crypto/sha256" "encoding/base64" "encoding/json" + "errors" "fmt" "io" "log/slog" @@ -97,6 +98,10 @@ func (htx *HTXClient) GetSecret() string { return htx.secret } +func (htx *HTXClient) GetSignVersion() string { + return htx.signVersion +} + func (htx *HTXClient) GetHeaders() (map[string]string, error) { return map[string]string{ "Content-Type": "application/json", @@ -104,32 +109,42 @@ func (htx *HTXClient) GetHeaders() (map[string]string, error) { }, nil } -func (htx *HTXClient) GenSignatureValues(req HTTPRequest) (url.Values, error) { - parameters := url.Values{} +var ErrAuth = errors.New("auth error, you should reinitialize client using key and secret") - if req.QueryParams != nil { - q, err := goquery.Values(req.QueryParams) - if err != nil { - return nil, err - } - parameters = q +func (htx *HTXClient) CheckAuth() error { + if htx.GetKey() == "" || htx.GetSecret() == "" || htx.GetSignVersion() == "" { + return ErrAuth } - parameters.Add("AccessKeyId", htx.key) - parameters.Add("SignatureMethod", "HmacSHA256") - parameters.Add("SignatureVersion", htx.signVersion) - parameters.Add("Timestamp", time.Now().UTC().Format("2006-01-02T15:04:05")) + return nil +} - return parameters, nil +func (htx *HTXClient) GenAuthParams() DefaultAuthParam { + return DefaultAuthParam{ + AccessKeyId: htx.key, + SignatureMethod: "HmacSHA256", + SignatureVersion: htx.signVersion, + Timestamp: time.Now().UTC().Format("2006-01-02T15:04:05"), + } } -func (htx *HTXClient) NormalizeRequestContent(req HTTPRequest, parameters url.Values) (string, error) { - if req.Method == "" || req.BaseURL == "" || req.Path == "" || parameters.Encode() == "" { - return "", fmt.Errorf("gen signature error: method(%s), baseurl(%s), path(%s) and parameters(%s) should not be empty", - req.Method, req.BaseURL, req.Path, parameters.Encode()) +func (htx *HTXClient) NormalizeRequestContent(req HTTPRequest, tempQuery any) (string, error) { + if req.Method == "" || req.BaseURL == "" || req.Path == "" { + return "", fmt.Errorf("gen signature error: method(%s), baseurl(%s) and path(%s) should not be empty", + req.Method, req.BaseURL, req.Path) } - url, err := url.Parse(req.BaseURL + req.Path) + parameters := url.Values{} + + if tempQuery != nil { + q, err := goquery.Values(tempQuery) + if err != nil { + return "", err + } + parameters = q + } + + urls, err := url.Parse(req.BaseURL + req.Path) if err != nil { return "", err } @@ -137,7 +152,7 @@ func (htx *HTXClient) NormalizeRequestContent(req HTTPRequest, parameters url.Va var sb strings.Builder sb.WriteString(req.Method) sb.WriteString("\n") - sb.WriteString(url.Host) + sb.WriteString(urls.Host) sb.WriteString("\n") sb.WriteString(req.Path) sb.WriteString("\n") @@ -171,7 +186,11 @@ func (htx *HTXClient) SendHTTPRequest(ctx context.Context, req HTTPRequest) (*HT } if req.Query != nil { - url.RawQuery = req.Query.Encode() + q, err := goquery.Values(req.Query) + if err != nil { + return nil, err + } + url.RawQuery = q.Encode() } request, err := http.NewRequestWithContext(ctx, req.Method, url.String(), body) diff --git a/htx/utils/request.go b/htx/utils/request.go index 3394ddd..e1f1c73 100644 --- a/htx/utils/request.go +++ b/htx/utils/request.go @@ -20,16 +20,17 @@ package utils import ( "encoding/json" "net/url" + + goquery "github.com/google/go-querystring/query" ) type HTTPRequest struct { - BaseURL string - Path string - Method string - Headers map[string]string - Query url.Values - QueryParams any - Body any + BaseURL string + Path string + Method string + Headers map[string]string + Query any + Body any } // RequestURI returns the request uri. @@ -40,7 +41,11 @@ func (h *HTTPRequest) RequestURI() (string, error) { } if h.Query != nil { - url.RawQuery = h.Query.Encode() + q, err := goquery.Values(h.Query) + if err != nil { + return "", err + } + url.RawQuery = q.Encode() } return url.RequestURI(), nil @@ -62,3 +67,21 @@ func (h *HTTPRequest) RequestBody() (string, error) { return body, nil } + +type V1Response struct { + Status string `json:"messsage"` + Ch string `json:"ch"` +} + +type V2Response struct { + Code int `json:"code"` + Message string `json:"messsage"` +} + +type DefaultAuthParam struct { + AccessKeyId string `url:"AccessKeyId,omitempty" validate:"omitempty"` + SignatureMethod string `url:"SignatureMethod,omitempty" validate:"omitempty"` + SignatureVersion string `url:"SignatureVersion,omitempty" validate:"omitempty"` + Timestamp string `url:"Timestamp,omitempty" validate:"omitempty"` + Signature string `url:"Signature,omitempty" validate:"omitempty"` +} diff --git a/htx/utils/response.go b/htx/utils/response.go index 4d2faec..99f013c 100644 --- a/htx/utils/response.go +++ b/htx/utils/response.go @@ -88,8 +88,7 @@ func (r *HTTPResponse) Error() string { body = []byte(NIL) } - m := fmt.Sprintf("[Parse]Failure: parse JSON body failed because %s, %s %s with body=%s, respond code=%d body=%s", - err.Error(), + m := fmt.Sprintf("[Parse]Failure: parse JSON body failed, %s %s with body=%s, respond code=%d body=%s", r.Req.Method, uri, reqBody, diff --git a/htx/utils/vars.go b/htx/utils/vars.go index 4b90054..122020a 100644 --- a/htx/utils/vars.go +++ b/htx/utils/vars.go @@ -24,8 +24,5 @@ var ( var NIL = "" -// ApiKeyVersionV1 is v1 api key version -const ApiKeyVersionV1 = "1" - // ApiKeyVersionV2 is v2 api key version const ApiKeyVersionV2 = "2" diff --git a/kucoin/rest/utils/response.go b/kucoin/rest/utils/response.go index e439864..a038450 100644 --- a/kucoin/rest/utils/response.go +++ b/kucoin/rest/utils/response.go @@ -89,8 +89,7 @@ func (r *HTTPResponse) Error() string { body = []byte(NIL) } - m := fmt.Sprintf("[Parse]Failure: parse JSON body failed because %s, %s %s with body=%s, respond code=%d body=%s", - err.Error(), + m := fmt.Sprintf("[Parse]Failure: parse JSON body failed, %s %s with body=%s, respond code=%d body=%s", r.Req.Method, uri, reqBody,