diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index fcf9e5bc626..f375b85d413 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -480,11 +480,11 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor s } // GetOrderByID returns a single order by order id. -func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientOID, userNativeCurrency string) (*GetOrderResponse, error) { +func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientOID, userNativeCurrency string) (*SingleOrder, error) { if orderID == "" { return nil, errOrderIDEmpty } - var resp GetOrderResponse + var resp SingleOrder vals := url.Values{} if clientOID != "" { vals.Set("client_order_id", clientOID) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index dd6e69ddd56..8f6c4fcb3a7 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -274,7 +274,14 @@ func orderTestHelper(t *testing.T, orderSide string) *GetAllOrdersResp { if ordIDs == nil || len(ordIDs.Orders) == 0 { t.Skip(skipInsufficientOrders) } - return ordIDs + for i := range ordIDs.Orders { + if ordIDs.Orders[i].Status == order.Open.String() { + ordIDs.Orders = ordIDs.Orders[i : i+1] + return ordIDs + } + } + t.Skip(skipInsufficientOrders) + return nil } func TestCancelOrders(t *testing.T) { @@ -1580,9 +1587,12 @@ func TestWsHandleData(t *testing.T) { continue } }() - _, err := c.wsHandleData(nil, 0) + mockJSON := []byte(`{"type": "error"}`) + _, err := c.wsHandleData(mockJSON, 0) + assert.Error(t, err) + _, err = c.wsHandleData(nil, 0) assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) - mockJSON := []byte(`{"sequence_num": "l"}`) + mockJSON = []byte(`{"sequence_num": "l"}`) _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, strconv.ErrSyntax) mockJSON = []byte(`{"sequence_num": 1, /\\/"""}`) @@ -1601,9 +1611,6 @@ func TestWsHandleData(t *testing.T) { mockJSON = []byte(`{"sequence_num": 0, "channel": "status", "events": [{"type": "moo"}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) - mockJSON = []byte(`{"sequence_num": 0, "channel": "error", "events": [{"type": "moo"}]}`) - _, err = c.wsHandleData(mockJSON, 0) - assert.NoError(t, err) mockJSON = []byte(`{"sequence_num": 0, "channel": "ticker", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.ErrorAs(t, err, &targetErr) @@ -1717,7 +1724,7 @@ func TestSubscribeUnsubscribe(t *testing.T) { func TestCheckSubscriptions(t *testing.T) { t.Parallel() - c := &CoinbasePro{ + c := &CoinbasePro{ //nolint:govet // Intentional shadow to avoid future copy/paste mistakes Base: exchange.Base{ Config: &config.Exchange{ Features: &config.FeaturesConfig{ diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 922eaf777d7..b5bb8ebb1c7 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -298,6 +298,10 @@ type EditOrderPreviewResp struct { AverageFilledPrice float64 `json:"average_filled_price,string"` } +type SingleOrder struct { + Order GetOrderResponse `json:"order"` +} + // GetOrderResponse contains information on an order, returned by GetOrderByID // and IterativeGetAllOrders, and used in GetAllOrdersResp type GetOrderResponse struct { @@ -313,7 +317,7 @@ type GetOrderResponse struct { CompletionPercentage float64 `json:"completion_percentage,string"` FilledSize float64 `json:"filled_size,string"` AverageFilledPrice float64 `json:"average_filled_price,string"` - Fee float64 `json:"fee,string"` + Fee types.Number `json:"fee"` NumberOfFills int64 `json:"num_fills,string"` FilledValue float64 `json:"filled_value,string"` PendingCancel bool `json:"pending_cancel"` @@ -337,6 +341,9 @@ type GetOrderResponse struct { Size float64 `json:"size,string"` ReplaceAcceptTimestamp time.Time `json:"replace_accept_timestamp"` } `json:"edit_history"` + Leverage types.Number `json:"leverage"` + MarginType string `json:"margin_type"` + RetailPortfolioID string `json:"retail_portfolio_id"` } // FillResponse contains fill information, returned by GetFills diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 937fcb62cf8..c88d07eb91a 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -50,7 +50,9 @@ var subscriptionNames = map[string]string{ var defaultSubscriptions = subscription.List{ {Enabled: true, Channel: subscription.HeartbeatChannel}, - {Enabled: true, Channel: "status"}, + // Subscriptions to status return an "authentication failure" error, despite the endpoint not being authenticated + // and other authenticated channels working fine. + {Enabled: false, Channel: "status"}, {Enabled: true, Asset: asset.Spot, Channel: subscription.TickerChannel}, {Enabled: true, Asset: asset.Spot, Channel: subscription.CandlesChannel}, {Enabled: true, Asset: asset.Spot, Channel: subscription.AllTradesChannel}, @@ -108,6 +110,10 @@ func (c *CoinbasePro) wsReadData() { // wsHandleData handles all the websocket data coming from the websocket connection func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, error) { var warnString string + ertype, _, _, err := jsonparser.Get(respRaw, "type") + if err == nil && string(ertype) == "error" { + return warnString, errors.New(string(respRaw)) + } seqData, _, _, err := jsonparser.Get(respRaw, "sequence_num") if err != nil { return warnString, err @@ -140,8 +146,6 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err return warnString, err } c.Websocket.DataHandler <- wsStatus - case "error": - c.Websocket.DataHandler <- errors.New(string(respRaw)) case "ticker", "ticker_batch": wsTicker := []WebsocketTickerHolder{} err = json.Unmarshal(data, &wsTicker) diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 539f5989e39..665515258f8 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -555,7 +555,7 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. if err != nil { return nil, err } - subResp.Fee = feeResp.TotalFees + subResp.Fee = feeResp.Order.TotalFees } return subResp, nil } @@ -651,7 +651,7 @@ func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair cur if err != nil { return nil, err } - response := c.getOrderRespToOrderDetail(genOrderDetail, pair, assetItem) + response := c.getOrderRespToOrderDetail(&genOrderDetail.Order, pair, assetItem) fillData, err := c.GetFills(ctx, orderID, "", "", time.Time{}, time.Now(), 2<<15-1) if err != nil { return nil, err @@ -819,7 +819,11 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder } orders := make([]order.Detail, len(respOrders)) for i := range respOrders { - orderRec := c.getOrderRespToOrderDetail(&respOrders[i], req.Pairs[i], asset.Spot) + tempPair, err := currency.NewPairFromString(respOrders[i].ProductID) + if err != nil { + return nil, err + } + orderRec := c.getOrderRespToOrderDetail(&respOrders[i], tempPair, asset.Spot) orders[i] = *orderRec } return req.Filter(c.Name, orders), nil @@ -864,7 +868,11 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder } orders := make([]order.Detail, len(ord)) for i := range ord { - singleOrder := c.getOrderRespToOrderDetail(&ord[i], req.Pairs[0], req.AssetType) + tempPair, err := currency.NewPairFromString(ord[i].ProductID) + if err != nil { + return nil, err + } + singleOrder := c.getOrderRespToOrderDetail(&ord[i], tempPair, req.AssetType) orders[i] = *singleOrder } return req.Filter(c.Name, orders), nil