diff --git a/exchanges/asset/asset_test.go b/exchanges/asset/asset_test.go index 13105dc1f99..249c117faf6 100644 --- a/exchanges/asset/asset_test.go +++ b/exchanges/asset/asset_test.go @@ -21,6 +21,19 @@ func TestString(t *testing.T) { } } +func TestUpper(t *testing.T) { + t.Parallel() + a := Spot + if a.Upper() != "SPOT" { + t.Fatal("TestUpper returned an unexpected result") + } + + a = 0 + if a.Upper() != "" { + t.Fatal("TestUpper returned an unexpected result") + } +} + func TestToStringArray(t *testing.T) { t.Parallel() a := Items{Spot, Futures} diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index ad5d32380ca..588638309a6 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -149,18 +149,17 @@ var ( ) // GetAllAccounts returns information on all trading accounts associated with the API key -func (c *CoinbasePro) GetAllAccounts(ctx context.Context, limit uint8, cursor string) (AllAccountsResponse, error) { +func (c *CoinbasePro) GetAllAccounts(ctx context.Context, limit uint8, cursor string) (*AllAccountsResponse, error) { + vals := url.Values{} + if limit != 0 { + vals.Set("limit", strconv.FormatUint(uint64(limit), 10)) + } + if cursor != "" { + vals.Set("cursor", cursor) + } var resp AllAccountsResponse - - var params Params - params.urlVals = url.Values{} - params.urlVals.Set("limit", strconv.FormatInt(int64(limit), 10)) - params.urlVals.Set("cursor", cursor) - - pathParams := common.EncodeURLValues("", params.urlVals) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseAccounts, pathParams, nil, true, &resp, nil) + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbaseAccounts, vals, nil, true, &resp, nil) } // GetAccountByID returns information for a single account @@ -168,81 +167,63 @@ func (c *CoinbasePro) GetAccountByID(ctx context.Context, accountID string) (*Ac if accountID == "" { return nil, errAccountIDEmpty } - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseAccounts, accountID) + path := coinbaseV3 + coinbaseAccounts + "/" + accountID resp := OneAccountResponse{} - return &resp.Account, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, true, &resp, nil) + path, nil, nil, true, &resp, nil) } // GetBestBidAsk returns the best bid/ask for all products. Can be filtered to certain products // by passing through additional strings -func (c *CoinbasePro) GetBestBidAsk(ctx context.Context, products []string) (BestBidAsk, error) { - var params Params - params.urlVals = url.Values{} - if len(products) > 0 { - for x := range products { - params.urlVals.Add("product_ids", products[x]) - } +func (c *CoinbasePro) GetBestBidAsk(ctx context.Context, products []string) ([]ProductBook, error) { + vals := url.Values{} + for x := range products { + vals.Add("product_ids", products[x]) } - - pathParams := common.EncodeURLValues("", params.urlVals) - var resp BestBidAsk - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseBestBidAsk, pathParams, nil, true, &resp, nil) + return resp.Pricebooks, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbaseBestBidAsk, vals, nil, true, &resp, nil) } // GetProductBook returns a list of bids/asks for a single product -func (c *CoinbasePro) GetProductBook(ctx context.Context, productID string, limit uint16) (ProductBook, error) { +func (c *CoinbasePro) GetProductBook(ctx context.Context, productID string, limit uint16) (*ProductBook, error) { if productID == "" { - return ProductBook{}, errProductIDEmpty + return nil, errProductIDEmpty + } + vals := url.Values{} + vals.Set("product_id", productID) + if limit != 0 { + vals.Set("limit", strconv.FormatInt(int64(limit), 10)) } - var params Params - params.urlVals = url.Values{} - params.urlVals.Set("product_id", productID) - params.urlVals.Set("limit", strconv.FormatInt(int64(limit), 10)) - - pathParams := common.EncodeURLValues("", params.urlVals) - var resp ProductBookResponse - - return resp.Pricebook, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseProductBook, pathParams, nil, true, &resp, nil) + return &resp.Pricebook, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbaseProductBook, vals, nil, true, &resp, nil) } // GetAllProducts returns information on all currency pairs that are available for trading -func (c *CoinbasePro) GetAllProducts(ctx context.Context, limit, offset int32, productType, contractExpiryType, expiringContractStatus string, productIDs []string) (AllProducts, error) { - var params Params - params.urlVals = url.Values{} - params.urlVals.Set("limit", strconv.FormatInt(int64(limit), 10)) - params.urlVals.Set("offset", strconv.FormatInt(int64(offset), 10)) - +func (c *CoinbasePro) GetAllProducts(ctx context.Context, limit, offset int32, productType, contractExpiryType, expiringContractStatus string, productIDs []string) (*AllProducts, error) { + vals := url.Values{} + if limit != 0 { + vals.Set("limit", strconv.FormatInt(int64(limit), 10)) + } + if offset != 0 { + vals.Set("offset", strconv.FormatInt(int64(offset), 10)) + } if productType != "" { - params.urlVals.Set("product_type", productType) + vals.Set("product_type", productType) } - if contractExpiryType != "" { - params.urlVals.Set("contract_expiry_type", contractExpiryType) + vals.Set("contract_expiry_type", contractExpiryType) } - if expiringContractStatus != "" { - params.urlVals.Set("expiring_contract_status", expiringContractStatus) + vals.Set("expiring_contract_status", expiringContractStatus) } - - if len(productIDs) > 0 { - for x := range productIDs { - params.urlVals.Add("product_ids", productIDs[x]) - } + for x := range productIDs { + vals.Add("product_ids", productIDs[x]) } - - pathParams := common.EncodeURLValues("", params.urlVals) - var products AllProducts - - return products, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseProducts, pathParams, nil, true, &products, nil) + return &products, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbaseProducts, vals, nil, true, &products, nil) } // GetProductByID returns information on a single specified currency pair @@ -250,48 +231,35 @@ func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string) (*Pr if productID == "" { return nil, errProductIDEmpty } - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseProducts, productID) - + path := coinbaseV3 + coinbaseProducts + "/" + productID resp := Product{} - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, true, &resp, nil) + path, nil, nil, true, &resp, nil) } // GetHistoricRates returns historic rates for a product. Rates are returned in // grouped buckets based on requested granularity. Requests that return more than // 300 data points are rejected -func (c *CoinbasePro) GetHistoricRates(ctx context.Context, productID, granularity string, startDate, endDate time.Time) (History, error) { +func (c *CoinbasePro) GetHistoricRates(ctx context.Context, productID, granularity string, startDate, endDate time.Time) ([]CandleStruct, error) { var resp History - if productID == "" { - return resp, errProductIDEmpty + return nil, errProductIDEmpty } - allowedGranularities := [8]string{granOneMin, granFiveMin, granFifteenMin, granThirtyMin, granOneHour, granTwoHour, granSixHour, granOneDay} validGran, _ := common.InArray(granularity, allowedGranularities) if !validGran { - return resp, fmt.Errorf("invalid granularity %v, allowed granularities are: %+v", + return nil, fmt.Errorf("invalid granularity %v, allowed granularities are: %+v", granularity, allowedGranularities) } - - var params Params - params.urlVals = url.Values{} - - params.urlVals.Set("start", strconv.FormatInt(startDate.Unix(), 10)) - params.urlVals.Set("end", strconv.FormatInt(endDate.Unix(), 10)) - params.urlVals.Set("granularity", granularity) - - pathParams := common.EncodeURLValues("", params.urlVals) - - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseProducts, productID, coinbaseCandles) - + vals := url.Values{} + vals.Set("start", strconv.FormatInt(startDate.Unix(), 10)) + vals.Set("end", strconv.FormatInt(endDate.Unix(), 10)) + vals.Set("granularity", granularity) + path := coinbaseV3 + coinbaseProducts + "/" + productID + "/" + coinbaseCandles err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, true, &resp, nil) - - return resp, err + path, vals, nil, true, &resp, nil) + return resp.Candles, err } // GetTicker returns snapshot information about the last trades (ticks) and best bid/ask. @@ -300,21 +268,18 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uin if productID == "" { return nil, errProductIDEmpty } - path := fmt.Sprintf( - "%s%s/%s/%s", coinbaseV3, coinbaseProducts, productID, coinbaseTicker) - - var params Params - params.urlVals = url.Values{} - params.urlVals.Set("limit", strconv.FormatInt(int64(limit), 10)) - params.urlVals.Set("start", strconv.FormatInt(startDate.Unix(), 10)) - params.urlVals.Set("end", strconv.FormatInt(endDate.Unix(), 10)) - - pathParams := common.EncodeURLValues("", params.urlVals) - + path := coinbaseV3 + coinbaseProducts + "/" + productID + "/" + coinbaseTicker + vals := url.Values{} + vals.Set("limit", strconv.FormatInt(int64(limit), 10)) + if !startDate.IsZero() && !startDate.Equal(time.Time{}) { + vals.Set("start", strconv.FormatInt(startDate.Unix(), 10)) + } + if !endDate.IsZero() && !endDate.Equal(time.Time{}) { + vals.Set("end", strconv.FormatInt(endDate.Unix(), 10)) + } var resp Ticker - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, true, &resp, nil) + path, vals, nil, true, &resp, nil) } // PlaceOrder places either a limit, market, or stop order @@ -328,59 +293,55 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side if amount == 0 { return nil, errAmountEmpty } - orderConfig, err := prepareOrderConfig(orderType, side, stopDirection, amount, limitPrice, stopPrice, endTime, postOnly) if err != nil { return nil, err } - - req := map[string]interface{}{"client_order_id": clientOID, "product_id": productID, - "side": side, "order_configuration": orderConfig, "self_trade_prevention_id": stpID, - "leverage": strconv.FormatFloat(leverage, 'f', -1, 64), "retail_portfolio_id": rpID} - - prepareMarginType(marginType, req) - + mt := formatMarginType(marginType) + req := map[string]interface{}{ + "client_order_id": clientOID, + "product_id": productID, + "side": side, + "order_configuration": orderConfig, + "self_trade_prevention_id": stpID, + "leverage": strconv.FormatFloat(leverage, 'f', -1, 64), + "retail_portfolio_id": rpID, + "margin_type": mt} var resp PlaceOrderResp - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - coinbaseV3+coinbaseOrders, "", req, true, &resp, nil) + coinbaseV3+coinbaseOrders, nil, req, true, &resp, nil) } // CancelOrders cancels orders by orderID. Can only cancel 100 orders per request -func (c *CoinbasePro) CancelOrders(ctx context.Context, orderIDs []string) (CancelOrderResp, error) { - var resp CancelOrderResp +func (c *CoinbasePro) CancelOrders(ctx context.Context, orderIDs []string) ([]OrderCancelDetail, error) { if len(orderIDs) == 0 { - return resp, errOrderIDEmpty + return nil, errOrderIDEmpty } - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseOrders, coinbaseBatchCancel) - + path := coinbaseV3 + coinbaseOrders + "/" + coinbaseBatchCancel req := map[string]interface{}{"order_ids": orderIDs} - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", + var resp CancelOrderResp + return resp.Results, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, &resp, nil) } // EditOrder edits an order to a new size or price. Only limit orders with a good-till-cancelled time // in force can be edited -func (c *CoinbasePro) EditOrder(ctx context.Context, orderID string, size, price float64) (SuccessBool, error) { - var resp SuccessBool - +func (c *CoinbasePro) EditOrder(ctx context.Context, orderID string, size, price float64) (bool, error) { if orderID == "" { - return resp, errOrderIDEmpty + return false, errOrderIDEmpty } if size == 0 && price == 0 { - return resp, errSizeAndPriceZero + return false, errSizeAndPriceZero } - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseOrders, coinbaseEdit) - - req := map[string]interface{}{"order_id": orderID, "size": strconv.FormatFloat(size, 'f', -1, 64), - "price": strconv.FormatFloat(price, 'f', -1, 64)} - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", + path := coinbaseV3 + coinbaseOrders + "/" + coinbaseEdit + req := map[string]interface{}{ + "order_id": orderID, + "size": strconv.FormatFloat(size, 'f', -1, 64), + "price": strconv.FormatFloat(price, 'f', -1, 64)} + var resp SuccessBool + return resp.Success, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, &resp, nil) } @@ -393,101 +354,94 @@ func (c *CoinbasePro) EditOrderPreview(ctx context.Context, orderID string, size if size == 0 && price == 0 { return nil, errSizeAndPriceZero } - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseOrders, coinbaseEditPreview) - - req := map[string]interface{}{"order_id": orderID, "size": strconv.FormatFloat(size, 'f', -1, 64), - "price": strconv.FormatFloat(price, 'f', -1, 64)} - + path := coinbaseV3 + coinbaseOrders + "/" + coinbaseEditPreview + req := map[string]interface{}{ + "order_id": orderID, + "size": strconv.FormatFloat(size, 'f', -1, 64), + "price": strconv.FormatFloat(price, 'f', -1, 64)} var resp *EditOrderPreviewResp - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, &resp, nil) } // GetAllOrders lists orders, filtered by their status -func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCurrency, orderType, orderSide, cursor, productType, orderPlacementSource, contractExpiryType, retailPortfolioID string, orderStatus, assetFilters []string, limit int32, startDate, endDate time.Time) (GetAllOrdersResp, error) { - var resp GetAllOrdersResp - +func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCurrency, orderType, orderSide, cursor, productType, orderPlacementSource, contractExpiryType, retailPortfolioID string, orderStatus, assetFilters []string, limit int32, startDate, endDate time.Time) (*GetAllOrdersResp, error) { var params Params - params.urlVals = make(url.Values) + params.Values = make(url.Values) err := params.prepareDateString(startDate, endDate, startDateString, endDateString) if err != nil { - return resp, err + return nil, err } - if len(orderStatus) != 0 { - for x := range orderStatus { - if orderStatus[x] == "OPEN" && len(orderStatus) > 1 { - return resp, errOpenPairWithOtherTypes - } - params.urlVals.Add("order_status", orderStatus[x]) + for x := range orderStatus { + if orderStatus[x] == "OPEN" && len(orderStatus) > 1 { + return nil, errOpenPairWithOtherTypes } + params.Values.Add("order_status", orderStatus[x]) } - if len(assetFilters) != 0 { - for x := range assetFilters { - params.urlVals.Add("asset_filters", assetFilters[x]) - } + for x := range assetFilters { + params.Values.Add("asset_filters", assetFilters[x]) + } + if productID != "" { + params.Values.Set("product_id", productID) + } + if limit != 0 { + params.Values.Set("limit", strconv.FormatInt(int64(limit), 10)) + } + if cursor != "" { + params.Values.Set("cursor", cursor) } - - params.urlVals.Set("product_id", productID) - params.urlVals.Set("limit", strconv.FormatInt(int64(limit), 10)) - params.urlVals.Set("cursor", cursor) - if userNativeCurrency != "" { - params.urlVals.Set("user_native_currency", userNativeCurrency) + params.Values.Set("user_native_currency", userNativeCurrency) } if orderPlacementSource != "" { - params.urlVals.Set("order_placement_source", orderPlacementSource) + params.Values.Set("order_placement_source", orderPlacementSource) } if productType != "" { - params.urlVals.Set("product_type", productType) + params.Values.Set("product_type", productType) } if orderSide != "" { - params.urlVals.Set("order_side", orderSide) + params.Values.Set("order_side", orderSide) } if contractExpiryType != "" { - params.urlVals.Set("contract_expiry_type", contractExpiryType) + params.Values.Set("contract_expiry_type", contractExpiryType) } if retailPortfolioID != "" { - params.urlVals.Set("retail_portfolio_id", retailPortfolioID) + params.Values.Set("retail_portfolio_id", retailPortfolioID) } if orderType != "" { - params.urlVals.Set("order_type", orderType) + params.Values.Set("order_type", orderType) } - - pathParams := common.EncodeURLValues("", params.urlVals) - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseOrders, coinbaseHistorical, coinbaseBatch) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, - pathParams, nil, true, &resp, nil) + path := coinbaseV3 + coinbaseOrders + "/" + coinbaseHistorical + "/" + coinbaseBatch + var resp GetAllOrdersResp + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, + params.Values, nil, true, &resp, nil) } // GetFills returns information of recent fills on the specified order -func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor string, startDate, endDate time.Time, limit uint16) (FillResponse, error) { - var resp FillResponse +func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor string, startDate, endDate time.Time, limit uint16) (*FillResponse, error) { var params Params - params.urlVals = url.Values{} + params.Values = url.Values{} err := params.prepareDateString(startDate, endDate, "start_sequence_timestamp", "end_sequence_timestamp") if err != nil { - return resp, err + return nil, err } - if orderID != "" { - params.urlVals.Set("order_id", orderID) + params.Values.Set("order_id", orderID) } if productID != "" { - params.urlVals.Set("product_id", productID) + params.Values.Set("product_id", productID) } - - params.urlVals.Set("limit", strconv.FormatInt(int64(limit), 10)) - params.urlVals.Set("cursor", cursor) - - pathParams := common.EncodeURLValues("", params.urlVals) - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseOrders, coinbaseHistorical, coinbaseFills) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, - pathParams, nil, true, &resp, nil) + if limit != 0 { + params.Values.Set("limit", strconv.FormatInt(int64(limit), 10)) + } + if cursor != "" { + params.Values.Set("cursor", cursor) + } + path := coinbaseV3 + coinbaseOrders + "/" + coinbaseHistorical + "/" + coinbaseFills + var resp FillResponse + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, + params.Values, nil, true, &resp, nil) } // GetOrderByID returns a single order by order id. @@ -496,15 +450,15 @@ func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientOID, user return nil, errOrderIDEmpty } var resp GetOrderResponse - var params Params - params.urlVals = url.Values{} - params.urlVals.Set("client_order_id", clientOID) - params.urlVals.Set("user_native_currency", userNativeCurrency) - - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseOrders, coinbaseHistorical, orderID) - pathParams := common.EncodeURLValues("", params.urlVals) - - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, pathParams, nil, true, &resp, nil) + vals := url.Values{} + if clientOID != "" { + vals.Set("client_order_id", clientOID) + } + if userNativeCurrency != "" { + vals.Set("user_native_currency", userNativeCurrency) + } + path := coinbaseV3 + coinbaseOrders + "/" + coinbaseHistorical + "/" + orderID + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, vals, nil, true, &resp, nil) } // PreviewOrder simulates the results of an order request @@ -512,83 +466,73 @@ func (c *CoinbasePro) PreviewOrder(ctx context.Context, productID, side, orderTy if amount == 0 { return nil, errAmountEmpty } - orderConfig, err := prepareOrderConfig(orderType, side, stopDirection, amount, limitPrice, stopPrice, endTime, postOnly) if err != nil { return nil, err } - commissionRate := map[string]string{"value": strconv.FormatFloat(commissionValue, 'f', -1, 64)} - - req := map[string]interface{}{"product_id": productID, "side": side, "commission_rate": commissionRate, - "order_configuration": orderConfig, "is_max": isMax, + mt := formatMarginType(marginType) + req := map[string]interface{}{ + "product_id": productID, + "side": side, + "commission_rate": commissionRate, + "order_configuration": orderConfig, + "is_max": isMax, "tradable_balance": strconv.FormatFloat(tradableBalance, 'f', -1, 64), - "skip_fcm_risk_check": skipFCMRiskCheck, "leverage": strconv.FormatFloat(leverage, 'f', -1, 64)} - - prepareMarginType(marginType, req) - + "skip_fcm_risk_check": skipFCMRiskCheck, + "leverage": strconv.FormatFloat(leverage, 'f', -1, 64), + "margin_type": mt} var resp *PreviewOrderResp - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseOrders, coinbasePreview) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", req, true, + path := coinbaseV3 + coinbaseOrders + "/" + coinbasePreview + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, &resp, nil) } // GetAllPortfolios returns a list of portfolios associated with the user -func (c *CoinbasePro) GetAllPortfolios(ctx context.Context, portfolioType string) (AllPortfolioResponse, error) { +func (c *CoinbasePro) GetAllPortfolios(ctx context.Context, portfolioType string) ([]SimplePortfolioData, error) { var resp AllPortfolioResponse - - var params Params - params.urlVals = url.Values{} - + vals := url.Values{} if portfolioType != "" { - params.urlVals.Set("portfolio_type", portfolioType) + vals.Set("portfolio_type", portfolioType) } - - pathParams := common.EncodeURLValues("", params.urlVals) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbasePortfolios, pathParams, nil, true, &resp, nil) + return resp.Portfolios, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbasePortfolios, vals, nil, true, &resp, nil) } // CreatePortfolio creates a new portfolio -func (c *CoinbasePro) CreatePortfolio(ctx context.Context, name string) (SimplePortfolioResponse, error) { - var resp SimplePortfolioResponse - +func (c *CoinbasePro) CreatePortfolio(ctx context.Context, name string) (*SimplePortfolioResponse, error) { if name == "" { - return resp, errNameEmpty + return nil, errNameEmpty } - req := map[string]interface{}{"name": name} - + var resp *SimplePortfolioResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - coinbaseV3+coinbasePortfolios, "", req, true, &resp, nil) + coinbaseV3+coinbasePortfolios, nil, req, true, &resp, nil) } // MovePortfolioFunds transfers funds between portfolios -func (c *CoinbasePro) MovePortfolioFunds(ctx context.Context, currency, from, to string, amount float64) (MovePortfolioFundsResponse, error) { - var resp MovePortfolioFundsResponse - +func (c *CoinbasePro) MovePortfolioFunds(ctx context.Context, currency, from, to string, amount float64) (*MovePortfolioFundsResponse, error) { if from == "" || to == "" { - return resp, errPortfolioIDEmpty + return nil, errPortfolioIDEmpty } if currency == "" { - return resp, errCurrencyEmpty + return nil, errCurrencyEmpty } if amount == 0 { - return resp, errAmountEmpty + return nil, errAmountEmpty } - - funds := FundsData{Value: strconv.FormatFloat(amount, 'f', -1, 64), Currency: currency} - - req := map[string]interface{}{"source_portfolio_uuid": from, "target_portfolio_uuid": to, "funds": funds} - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbasePortfolios, coinbaseMoveFunds) - + funds := FundsData{ + Value: strconv.FormatFloat(amount, 'f', -1, 64), + Currency: currency} + req := map[string]interface{}{ + "source_portfolio_uuid": from, + "target_portfolio_uuid": to, + "funds": funds} + path := coinbaseV3 + coinbasePortfolios + "/" + coinbaseMoveFunds + var resp *MovePortfolioFundsResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", req, true, &resp, nil) + path, nil, req, true, &resp, nil) } // GetPortfolioByID provides detailed information on a single portfolio @@ -596,13 +540,10 @@ func (c *CoinbasePro) GetPortfolioByID(ctx context.Context, portfolioID string) if portfolioID == "" { return nil, errPortfolioIDEmpty } - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbasePortfolios, portfolioID) - + path := coinbaseV3 + coinbasePortfolios + "/" + portfolioID var resp DetailedPortfolioResponse - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, true, &resp, nil) + path, nil, nil, true, &resp, nil) } // DeletePortfolio deletes a portfolio @@ -610,65 +551,52 @@ func (c *CoinbasePro) DeletePortfolio(ctx context.Context, portfolioID string) e if portfolioID == "" { return errPortfolioIDEmpty } - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbasePortfolios, portfolioID) - - return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", nil, + path := coinbaseV3 + coinbasePortfolios + "/" + portfolioID + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil, true, nil, nil) } // EditPortfolio edits the name of a portfolio -func (c *CoinbasePro) EditPortfolio(ctx context.Context, portfolioID, name string) (SimplePortfolioResponse, error) { - var resp SimplePortfolioResponse - +func (c *CoinbasePro) EditPortfolio(ctx context.Context, portfolioID, name string) (*SimplePortfolioResponse, error) { if portfolioID == "" { - return resp, errPortfolioIDEmpty + return nil, errPortfolioIDEmpty } if name == "" { - return resp, errNameEmpty + return nil, errNameEmpty } - req := map[string]interface{}{"name": name} - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbasePortfolios, portfolioID) - + path := coinbaseV3 + coinbasePortfolios + "/" + portfolioID + var resp *SimplePortfolioResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, - path, "", req, true, &resp, nil) + path, nil, req, true, &resp, nil) } // GetFuturesBalanceSummary returns information on balances related to Coinbase Financial Markets // futures trading -func (c *CoinbasePro) GetFuturesBalanceSummary(ctx context.Context) (FuturesBalanceSummary, error) { - var resp FuturesBalanceSummary - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseCFM, coinbaseBalanceSummary) - +func (c *CoinbasePro) GetFuturesBalanceSummary(ctx context.Context) (*FuturesBalanceSummary, error) { + var resp *FuturesBalanceSummary + path := coinbaseV3 + coinbaseCFM + "/" + coinbaseBalanceSummary return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, true, &resp, nil) + path, nil, nil, true, &resp, nil) } // GetAllFuturesPositions returns a list of all open positions in CFM futures products -func (c *CoinbasePro) GetAllFuturesPositions(ctx context.Context) (AllFuturesPositions, error) { +func (c *CoinbasePro) GetAllFuturesPositions(ctx context.Context) ([]FuturesPosition, error) { var resp AllFuturesPositions - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseCFM, coinbasePositions) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, true, &resp, nil) + path := coinbaseV3 + coinbaseCFM + "/" + coinbasePositions + return resp.Positions, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, nil, nil, true, &resp, nil) } // GetFuturesPositionByID returns information on a single open position in CFM futures products -func (c *CoinbasePro) GetFuturesPositionByID(ctx context.Context, productID string) (FuturesPosition, error) { - var resp FuturesPosition - +func (c *CoinbasePro) GetFuturesPositionByID(ctx context.Context, productID string) (*FuturesPosition, error) { if productID == "" { - return resp, errProductIDEmpty + return nil, errProductIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseCFM, coinbasePositions, productID) - + path := coinbaseV3 + coinbaseCFM + "/" + coinbasePositions + "/" + productID + var resp *FuturesPosition return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, true, &resp, nil) + path, nil, nil, true, &resp, nil) } // ScheduleFuturesSweep schedules a sweep of funds from a CFTC-regulated futures account to a @@ -676,39 +604,31 @@ func (c *CoinbasePro) GetFuturesPositionByID(ctx context.Context, productID stri // business day, requests submitted after are processed in 2 business days. Only one // sweep request can be pending at a time. Funds transferred depend on the excess available // in the futures account. An amount of 0 will sweep all available excess funds -func (c *CoinbasePro) ScheduleFuturesSweep(ctx context.Context, amount float64) (SuccessBool, error) { - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseCFM, coinbaseSweeps, coinbaseSchedule) - +func (c *CoinbasePro) ScheduleFuturesSweep(ctx context.Context, amount float64) (bool, error) { + path := coinbaseV3 + coinbaseCFM + "/" + coinbaseSweeps + "/" + coinbaseSchedule req := make(map[string]interface{}) - if amount != 0 { req["usd_amount"] = strconv.FormatFloat(amount, 'f', -1, 64) } - var resp SuccessBool - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", req, true, &resp, nil) + return resp.Success, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, + path, nil, req, true, &resp, nil) } // ListFuturesSweeps returns information on pending and/or processing requests to sweep funds -func (c *CoinbasePro) ListFuturesSweeps(ctx context.Context) (ListFuturesSweepsResponse, error) { +func (c *CoinbasePro) ListFuturesSweeps(ctx context.Context) ([]SweepData, error) { var resp ListFuturesSweepsResponse - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseCFM, coinbaseSweeps) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, true, &resp, nil) + path := coinbaseV3 + coinbaseCFM + "/" + coinbaseSweeps + return resp.Sweeps, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, nil, nil, true, &resp, nil) } // CancelPendingFuturesSweep cancels a pending sweep request -func (c *CoinbasePro) CancelPendingFuturesSweep(ctx context.Context) (SuccessBool, error) { - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseCFM, coinbaseSweeps) - +func (c *CoinbasePro) CancelPendingFuturesSweep(ctx context.Context) (bool, error) { + path := coinbaseV3 + coinbaseCFM + "/" + coinbaseSweeps var resp SuccessBool - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, - path, "", nil, true, &resp, nil) + return resp.Success, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, + path, nil, nil, true, &resp, nil) } // AllocatePortfolio allocates funds to a position in your perpetuals portfolio @@ -725,168 +645,148 @@ func (c *CoinbasePro) AllocatePortfolio(ctx context.Context, portfolioID, produc if amount == 0 { return errAmountEmpty } - - req := map[string]interface{}{"portfolio_uuid": portfolioID, "symbol": productID, "currency": currency, - "amount": strconv.FormatFloat(amount, 'f', -1, 64)} - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseIntx, coinbaseAllocate) - + req := map[string]interface{}{ + "portfolio_uuid": portfolioID, + "symbol": productID, + "currency": currency, + "amount": strconv.FormatFloat(amount, 'f', -1, 64)} + path := coinbaseV3 + coinbaseIntx + "/" + coinbaseAllocate return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", req, true, nil, nil) + path, nil, req, true, nil, nil) } // GetPerpetualsPortfolioSummary returns a summary of your perpetuals portfolio -func (c *CoinbasePro) GetPerpetualsPortfolioSummary(ctx context.Context, portfolioID string) (PerpetualPortResponse, error) { - var resp PerpetualPortResponse - +func (c *CoinbasePro) GetPerpetualsPortfolioSummary(ctx context.Context, portfolioID string) (*PerpetualPortResponse, error) { if portfolioID == "" { - return resp, errPortfolioIDEmpty + return nil, errPortfolioIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseIntx, coinbasePortfolio, portfolioID) - + path := coinbaseV3 + coinbaseIntx + "/" + coinbasePortfolio + "/" + portfolioID + var resp *PerpetualPortResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, true, &resp, nil) + path, nil, nil, true, &resp, nil) } // GetAllPerpetualsPositions returns a list of all open positions in your perpetuals portfolio -func (c *CoinbasePro) GetAllPerpetualsPositions(ctx context.Context, portfolioID string) (AllPerpPosResponse, error) { - var resp AllPerpPosResponse - +func (c *CoinbasePro) GetAllPerpetualsPositions(ctx context.Context, portfolioID string) (*AllPerpPosResponse, error) { if portfolioID == "" { - return resp, errPortfolioIDEmpty + return nil, errPortfolioIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseIntx, coinbasePositions, portfolioID) - + path := coinbaseV3 + coinbaseIntx + "/" + coinbasePositions + "/" + portfolioID + var resp *AllPerpPosResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, true, &resp, nil) + path, nil, nil, true, &resp, nil) } // GetPerpetualsPositionByID returns information on a single open position in your perpetuals portfolio -func (c *CoinbasePro) GetPerpetualsPositionByID(ctx context.Context, portfolioID, productID string) (OnePerpPosResponse, error) { - var resp OnePerpPosResponse - +func (c *CoinbasePro) GetPerpetualsPositionByID(ctx context.Context, portfolioID, productID string) (*OnePerpPosResponse, error) { if portfolioID == "" { - return resp, errPortfolioIDEmpty + return nil, errPortfolioIDEmpty } if productID == "" { - return resp, errProductIDEmpty + return nil, errProductIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV3, coinbaseIntx, coinbasePositions, portfolioID, productID) - + path := coinbaseV3 + coinbaseIntx + "/" + coinbasePositions + "/" + portfolioID + "/" + productID + var resp *OnePerpPosResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, true, &resp, nil) + path, nil, nil, true, &resp, nil) } // GetTransactionSummary returns a summary of transactions with fee tiers, total volume, // and fees func (c *CoinbasePro) GetTransactionSummary(ctx context.Context, startDate, endDate time.Time, userNativeCurrency, productType, contractExpiryType string) (*TransactionSummary, error) { var params Params - params.urlVals = url.Values{} - + params.Values = url.Values{} err := params.prepareDateString(startDate, endDate, startDateString, endDateString) if err != nil { return nil, err } - if contractExpiryType != "" { - params.urlVals.Set("contract_expiry_type", contractExpiryType) + params.Values.Set("contract_expiry_type", contractExpiryType) } if productType != "" { - params.urlVals.Set("product_type", productType) + params.Values.Set("product_type", productType) + } + if userNativeCurrency != "" { + params.Values.Set("user_native_currency", userNativeCurrency) } - - params.urlVals.Set("user_native_currency", userNativeCurrency) - - pathParams := common.EncodeURLValues("", params.urlVals) - var resp TransactionSummary - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseTransactionSummary, pathParams, nil, true, &resp, nil) + coinbaseV3+coinbaseTransactionSummary, params.Values, nil, true, &resp, nil) } // CreateConvertQuote creates a quote for a conversion between two currencies. The trade_id returned // can be used to commit the trade, but that must be done within 10 minutes of the quote's creation -func (c *CoinbasePro) CreateConvertQuote(ctx context.Context, from, to, userIncentiveID, codeVal string, amount float64) (ConvertResponse, error) { - var resp ConvertResponse +func (c *CoinbasePro) CreateConvertQuote(ctx context.Context, from, to, userIncentiveID, codeVal string, amount float64) (*ConvertResponse, error) { if from == "" || to == "" { - return resp, errAccountIDEmpty + return nil, errAccountIDEmpty } if amount == 0 { - return resp, errAmountEmpty + return nil, errAmountEmpty } - - path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseConvert, coinbaseQuote) - - tIM := map[string]interface{}{"user_incentive_id": userIncentiveID, "code_val": codeVal} - - req := map[string]interface{}{"from_account": from, "to_account": to, - "amount": strconv.FormatFloat(amount, 'f', -1, 64), "trade_incentive_metadata": tIM} - + path := coinbaseV3 + coinbaseConvert + "/" + coinbaseQuote + tIM := map[string]interface{}{ + "user_incentive_id": userIncentiveID, + "code_val": codeVal} + req := map[string]interface{}{ + "from_account": from, + "to_account": to, + "amount": strconv.FormatFloat(amount, 'f', -1, 64), + "trade_incentive_metadata": tIM} + var resp *ConvertResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, - "", req, true, &resp, nil) + nil, req, true, &resp, nil) } // CommitConvertTrade commits a conversion between two currencies, using the trade_id returned // from CreateConvertQuote -func (c *CoinbasePro) CommitConvertTrade(ctx context.Context, tradeID, from, to string) (ConvertResponse, error) { - var resp ConvertResponse +func (c *CoinbasePro) CommitConvertTrade(ctx context.Context, tradeID, from, to string) (*ConvertResponse, error) { if tradeID == "" { - return resp, errTransactionIDEmpty + return nil, errTransactionIDEmpty } if from == "" || to == "" { - return resp, errAccountIDEmpty + return nil, errAccountIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseConvert, coinbaseTrade, tradeID) - - req := map[string]interface{}{"from_account": from, "to_account": to} - + path := coinbaseV3 + coinbaseConvert + "/" + coinbaseTrade + "/" + tradeID + req := map[string]interface{}{ + "from_account": from, + "to_account": to} + var resp *ConvertResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, - "", req, true, &resp, nil) + nil, req, true, &resp, nil) } // GetConvertTradeByID returns information on a conversion between two currencies -func (c *CoinbasePro) GetConvertTradeByID(ctx context.Context, tradeID, from, to string) (ConvertResponse, error) { - var resp ConvertResponse +func (c *CoinbasePro) GetConvertTradeByID(ctx context.Context, tradeID, from, to string) (*ConvertResponse, error) { if tradeID == "" { - return resp, errTransactionIDEmpty + return nil, errTransactionIDEmpty } if from == "" || to == "" { - return resp, errAccountIDEmpty + return nil, errAccountIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseConvert, coinbaseTrade, tradeID) - - req := map[string]interface{}{"from_account": from, "to_account": to} - + path := coinbaseV3 + coinbaseConvert + "/" + coinbaseTrade + "/" + tradeID + req := map[string]interface{}{ + "from_account": from, + "to_account": to} + var resp *ConvertResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, - "", req, true, &resp, nil) + nil, req, true, &resp, nil) } // GetV3Time returns the current server time, calling V3 of the API -func (c *CoinbasePro) GetV3Time(ctx context.Context) (ServerTimeV3, error) { - var resp ServerTimeV3 - +func (c *CoinbasePro) GetV3Time(ctx context.Context) (*ServerTimeV3, error) { + var resp *ServerTimeV3 return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseTime, "", nil, true, &resp, nil) + coinbaseV3+coinbaseTime, nil, nil, true, &resp, nil) } // ListNotifications lists the notifications the user is subscribed to -func (c *CoinbasePro) ListNotifications(ctx context.Context, pag PaginationInp) (ListNotificationsResponse, error) { - var resp ListNotificationsResponse - +func (c *CoinbasePro) ListNotifications(ctx context.Context, pag PaginationInp) (*ListNotificationsResponse, error) { + var resp *ListNotificationsResponse var params Params - params.urlVals = url.Values{} + params.Values = url.Values{} params.preparePagination(pag) - - pathParams := common.EncodeURLValues("", params.urlVals) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV2+coinbaseNotifications, pathParams, nil, false, &resp, nil) + coinbaseV2+coinbaseNotifications, params.Values, nil, false, &resp, nil) } // GetUserByID returns information about a user, given their ID @@ -894,45 +794,35 @@ func (c *CoinbasePro) GetUserByID(ctx context.Context, userID string) (*UserResp if userID == "" { return nil, errUserIDEmpty } - - path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseUsers, userID) - + path := coinbaseV2 + coinbaseUsers + "/" + userID var resp *UserResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, false, &resp, nil) + path, nil, nil, false, &resp, nil) } // GetCurrentUser returns information about the user associated with the API key func (c *CoinbasePro) GetCurrentUser(ctx context.Context) (*UserResponse, error) { var resp *UserResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV2+coinbaseUser, "", nil, false, &resp, nil) + coinbaseV2+coinbaseUser, nil, nil, false, &resp, nil) } // GetAuthInfo returns information about the scopes granted to the API key -func (c *CoinbasePro) GetAuthInfo(ctx context.Context) (AuthResponse, error) { - var resp AuthResponse - - path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseUser, coinbaseAuth) - +func (c *CoinbasePro) GetAuthInfo(ctx context.Context) (*AuthResponse, error) { + var resp *AuthResponse + path := coinbaseV2 + coinbaseUser + "/" + coinbaseAuth return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, false, &resp, nil) + path, nil, nil, false, &resp, nil) } // GetAllWallets lists all accounts associated with the API key -func (c *CoinbasePro) GetAllWallets(ctx context.Context, pag PaginationInp) (GetAllWalletsResponse, error) { - var resp GetAllWalletsResponse - +func (c *CoinbasePro) GetAllWallets(ctx context.Context, pag PaginationInp) (*GetAllWalletsResponse, error) { + var resp *GetAllWalletsResponse var params Params - params.urlVals = url.Values{} + params.Values = url.Values{} params.preparePagination(pag) - - pathParams := common.EncodeURLValues("", params.urlVals) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV2+coinbaseAccounts, pathParams, nil, false, &resp, nil) + coinbaseV2+coinbaseAccounts, params.Values, nil, false, &resp, nil) } // GetWalletByID returns information about a single wallet. In lieu of a wallet ID, @@ -941,20 +831,16 @@ func (c *CoinbasePro) GetWalletByID(ctx context.Context, walletID, currency stri if (walletID == "" && currency == "") || (walletID != "" && currency != "") { return nil, errCurrWalletConflict } - var path string - if walletID != "" { - path = fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseAccounts, walletID) + path = coinbaseV2 + coinbaseAccounts + "/" + walletID } if currency != "" { - path = fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseAccounts, currency) + path = coinbaseV2 + coinbaseAccounts + "/" + currency } - var resp *GenWalletResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, false, &resp, nil) + path, nil, nil, false, &resp, nil) } // CreateAddress generates a crypto address for depositing to the specified wallet @@ -962,35 +848,25 @@ func (c *CoinbasePro) CreateAddress(ctx context.Context, walletID, name string) if walletID == "" { return nil, errWalletIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseAddresses) - + path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseAddresses req := map[string]interface{}{"name": name} - var resp *GenAddrResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", req, false, &resp, nil) + path, nil, req, false, &resp, nil) } // GetAllAddresses returns information on all addresses associated with a wallet -func (c *CoinbasePro) GetAllAddresses(ctx context.Context, walletID string, pag PaginationInp) (GetAllAddrResponse, error) { - var resp GetAllAddrResponse - +func (c *CoinbasePro) GetAllAddresses(ctx context.Context, walletID string, pag PaginationInp) (*GetAllAddrResponse, error) { if walletID == "" { - return resp, errWalletIDEmpty + return nil, errWalletIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseAddresses) - + path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseAddresses var params Params - params.urlVals = url.Values{} + params.Values = url.Values{} params.preparePagination(pag) - - pathParams := common.EncodeURLValues("", params.urlVals) - + var resp *GetAllAddrResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, false, &resp, nil) + path, params.Values, nil, false, &resp, nil) } // GetAddressByID returns information on a single address associated with the specified wallet @@ -1001,38 +877,28 @@ func (c *CoinbasePro) GetAddressByID(ctx context.Context, walletID, addressID st if addressID == "" { return nil, errAddressIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseAddresses, - addressID) - + path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseAddresses + "/" + addressID var resp *GenAddrResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, false, &resp, nil) + path, nil, nil, false, &resp, nil) } // GetAddressTransactions returns a list of transactions associated with the specified address -func (c *CoinbasePro) GetAddressTransactions(ctx context.Context, walletID, addressID string, pag PaginationInp) (ManyTransactionsResp, error) { - var resp ManyTransactionsResp - +func (c *CoinbasePro) GetAddressTransactions(ctx context.Context, walletID, addressID string, pag PaginationInp) (*ManyTransactionsResp, error) { if walletID == "" { - return resp, errWalletIDEmpty + return nil, errWalletIDEmpty } if addressID == "" { - return resp, errAddressIDEmpty + return nil, errAddressIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, - coinbaseAddresses, addressID, coinbaseTransactions) - + path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseAddresses + "/" + addressID + "/" + + coinbaseTransactions var params Params - params.urlVals = url.Values{} + params.Values = url.Values{} params.preparePagination(pag) - - pathParams := common.EncodeURLValues("", params.urlVals) - + var resp *ManyTransactionsResp return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, false, &resp, nil) + path, params.Values, nil, false, &resp, nil) } // SendMoney can send funds to an email or cryptocurrency address (if "traType" is set to "send"), @@ -1057,40 +923,35 @@ func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, curr if currency == "" { return nil, errCurrencyEmpty } - - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseTransactions) - - req := map[string]interface{}{"type": traType, "to": to, - "amount": strconv.FormatFloat(amount, 'f', -1, 64), "currency": currency, - "description": description, "skip_notifications": skipNotifications, "idem": idem, + path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseTransactions + req := map[string]interface{}{ + "type": traType, + "to": to, + "amount": strconv.FormatFloat(amount, 'f', -1, 64), + "currency": currency, + "description": description, + "skip_notifications": skipNotifications, + "idem": idem, "to_financial_institution": toFinancialInstitution, "financial_institution_website": financialInstitutionWebsite, "destination_tag": destinationTag} - var resp *GenTransactionResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", req, false, &resp, nil) + path, nil, req, false, &resp, nil) } // GetAllTransactions returns a list of transactions associated with the specified wallet -func (c *CoinbasePro) GetAllTransactions(ctx context.Context, walletID string, pag PaginationInp) (ManyTransactionsResp, error) { - var resp ManyTransactionsResp - +func (c *CoinbasePro) GetAllTransactions(ctx context.Context, walletID string, pag PaginationInp) (*ManyTransactionsResp, error) { if walletID == "" { - return resp, errWalletIDEmpty + return nil, errWalletIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseTransactions) - + path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseTransactions var params Params - params.urlVals = url.Values{} + params.Values = url.Values{} params.preparePagination(pag) - - pathParams := common.EncodeURLValues("", params.urlVals) - + var resp *ManyTransactionsResp return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, false, &resp, nil) + path, params.Values, nil, false, &resp, nil) } // GetTransactionByID returns information on a single transaction associated with the @@ -1102,14 +963,10 @@ func (c *CoinbasePro) GetTransactionByID(ctx context.Context, walletID, transact if transactionID == "" { return nil, errTransactionIDEmpty } - - path := fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, - coinbaseTransactions, transactionID) - + path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseTransactions + "/" + transactionID var resp *GenTransactionResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, false, &resp, nil) + path, nil, nil, false, &resp, nil) } // FiatTransfer prepares and optionally processes a transfer of funds between the exchange and a @@ -1128,22 +985,21 @@ func (c *CoinbasePro) FiatTransfer(ctx context.Context, walletID, currency, paym if paymentMethod == "" { return nil, errPaymentMethodEmpty } - var path string switch transferType { case FiatDeposit: - path = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseDeposits) + path = coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseDeposits case FiatWithdrawal: - path = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseWithdrawals) + path = coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseWithdrawals } - - req := map[string]interface{}{"currency": currency, "payment_method": paymentMethod, - "amount": strconv.FormatFloat(amount, 'f', -1, 64), "commit": commit} - + req := map[string]interface{}{ + "currency": currency, + "payment_method": paymentMethod, + "amount": strconv.FormatFloat(amount, 'f', -1, 64), + "commit": commit} var resp *GenDeposWithdrResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", req, false, &resp, nil) + path, nil, req, false, &resp, nil) } // CommitTransfer processes a deposit/withdrawal that was created with the "commit" parameter set @@ -1155,57 +1011,43 @@ func (c *CoinbasePro) CommitTransfer(ctx context.Context, walletID, depositID st if depositID == "" { return nil, errDepositIDEmpty } - var path string switch transferType { case FiatDeposit: - path = fmt.Sprintf("%s%s/%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, - coinbaseDeposits, depositID, coinbaseCommit) + path = coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseDeposits + "/" + depositID + "/" + coinbaseCommit case FiatWithdrawal: - path = fmt.Sprintf("%s%s/%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, - coinbaseWithdrawals, depositID, coinbaseCommit) + path = coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseWithdrawals + "/" + depositID + "/" + coinbaseCommit } - var resp *GenDeposWithdrResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", nil, false, &resp, nil) + path, nil, nil, false, &resp, nil) } // GetAllFiatTransfers returns a list of transfers either to or from fiat payment methods and // the specified wallet -func (c *CoinbasePro) GetAllFiatTransfers(ctx context.Context, walletID string, pag PaginationInp, transferType FiatTransferType) (ManyDeposWithdrResp, error) { - var resp ManyDeposWithdrResp - +func (c *CoinbasePro) GetAllFiatTransfers(ctx context.Context, walletID string, pag PaginationInp, transferType FiatTransferType) (*ManyDeposWithdrResp, error) { if walletID == "" { - return resp, errWalletIDEmpty + return nil, errWalletIDEmpty } - var path string switch transferType { case FiatDeposit: - path = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseDeposits) + path = coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseDeposits case FiatWithdrawal: - path = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseWithdrawals) + path = coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseWithdrawals } - var params Params - params.urlVals = url.Values{} + params.Values = url.Values{} params.preparePagination(pag) - - pathParams := common.EncodeURLValues("", params.urlVals) - + var resp *ManyDeposWithdrResp err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, false, &resp, nil) - + path, params.Values, nil, false, &resp, nil) if err != nil { - return resp, err + return nil, err } - for i := range resp.Data { resp.Data[i].TransferType = transferType } - return resp, nil } @@ -1217,37 +1059,26 @@ func (c *CoinbasePro) GetFiatTransferByID(ctx context.Context, walletID, deposit if depositID == "" { return nil, errDepositIDEmpty } - var path string switch transferType { case FiatDeposit: - path = fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, - coinbaseDeposits, depositID) + path = coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseDeposits + "/" + depositID case FiatWithdrawal: - path = fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, - coinbaseWithdrawals, depositID) + path = coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseWithdrawals + "/" + depositID } - var resp *GenDeposWithdrResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, false, &resp, nil) + path, nil, nil, false, &resp, nil) } // GetAllPaymentMethods returns a list of all payment methods associated with the user's account -func (c *CoinbasePro) GetAllPaymentMethods(ctx context.Context, pag PaginationInp) (GetAllPaymentMethodsResp, error) { - var resp GetAllPaymentMethodsResp - - path := fmt.Sprintf("%s%s", coinbaseV2, coinbasePaymentMethods) - +func (c *CoinbasePro) GetAllPaymentMethods(ctx context.Context, pag PaginationInp) (*GetAllPaymentMethodsResp, error) { + var resp *GetAllPaymentMethodsResp var params Params - params.urlVals = url.Values{} + params.Values = url.Values{} params.preparePagination(pag) - - pathParams := common.EncodeURLValues("", params.urlVals) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, false, &resp, nil) + coinbaseV2+coinbasePaymentMethods, params.Values, nil, false, &resp, nil) } // GetPaymentMethodByID returns information on a single payment method associated with the user's @@ -1256,69 +1087,52 @@ func (c *CoinbasePro) GetPaymentMethodByID(ctx context.Context, paymentMethodID if paymentMethodID == "" { return nil, errPaymentMethodEmpty } - - path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbasePaymentMethods, paymentMethodID) - + path := coinbaseV2 + coinbasePaymentMethods + "/" + paymentMethodID var resp *GenPaymentMethodResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, false, &resp, nil) + path, nil, nil, false, &resp, nil) } // GetFiatCurrencies lists currencies that Coinbase knows about -func (c *CoinbasePro) GetFiatCurrencies(ctx context.Context) (GetFiatCurrenciesResp, error) { +func (c *CoinbasePro) GetFiatCurrencies(ctx context.Context) ([]FiatData, error) { var resp GetFiatCurrenciesResp - - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseCurrencies, &resp) + return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseCurrencies, &resp) } // GetCryptocurrencies lists cryptocurrencies that Coinbase knows about -func (c *CoinbasePro) GetCryptocurrencies(ctx context.Context) (GetCryptocurrenciesResp, error) { +func (c *CoinbasePro) GetCryptocurrencies(ctx context.Context) ([]CryptoData, error) { var resp GetCryptocurrenciesResp - - path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseCurrencies, coinbaseCrypto) - - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) + path := coinbaseV2 + coinbaseCurrencies + "/" + coinbaseCrypto + return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) } // GetExchangeRates returns exchange rates for the specified currency. If none is specified, // it defaults to USD -func (c *CoinbasePro) GetExchangeRates(ctx context.Context, currency string) (GetExchangeRatesResp, error) { - var resp GetExchangeRatesResp - - var params Params - params.urlVals = url.Values{} - - params.urlVals.Set("currency", currency) - - path := common.EncodeURLValues(coinbaseV2+coinbaseExchangeRates, params.urlVals) - +func (c *CoinbasePro) GetExchangeRates(ctx context.Context, currency string) (*GetExchangeRatesResp, error) { + var resp *GetExchangeRatesResp + vals := url.Values{} + vals.Set("currency", currency) + path := common.EncodeURLValues(coinbaseV2+coinbaseExchangeRates, vals) return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) } // GetPrice returns the price the spot/buy/sell price for the specified currency pair, // including the standard Coinbase fee of 1%, but excluding any other fees -func (c *CoinbasePro) GetPrice(ctx context.Context, currencyPair, priceType string) (GetPriceResp, error) { - var resp GetPriceResp - +func (c *CoinbasePro) GetPrice(ctx context.Context, currencyPair, priceType string) (*GetPriceResp, error) { var path string switch priceType { - case "spot": - path = fmt.Sprintf("%s%s/%s/spot", coinbaseV2, coinbasePrices, currencyPair) - case "buy": - path = fmt.Sprintf("%s%s/%s/buy", coinbaseV2, coinbasePrices, currencyPair) - case "sell": - path = fmt.Sprintf("%s%s/%s/sell", coinbaseV2, coinbasePrices, currencyPair) + case "spot", "buy", "sell": + path = coinbaseV2 + coinbasePrices + "/" + currencyPair + "/" + priceType default: - return resp, errInvalidPriceType + return nil, errInvalidPriceType } - + var resp *GetPriceResp return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) } // GetV2Time returns the current server time, calling V2 of the API -func (c *CoinbasePro) GetV2Time(ctx context.Context) (ServerTimeV2, error) { - resp := ServerTimeV2{} +func (c *CoinbasePro) GetV2Time(ctx context.Context) (*ServerTimeV2, error) { + var resp *ServerTimeV2 return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseTime, &resp) } @@ -1328,7 +1142,6 @@ func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path if err != nil { return err } - item := &request.Item{ Method: http.MethodGet, Path: endpoint + path, @@ -1337,14 +1150,13 @@ func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path HTTPDebugging: c.HTTPDebugging, HTTPRecording: c.HTTPRecording, } - return c.SendPayload(ctx, request.Unset, func() (*request.Item, error) { return item, nil }, request.UnauthenticatedRequest) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request -func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path, queryParams string, bodyParams map[string]interface{}, isVersion3 bool, result interface{}, returnHead *http.Header) (err error) { +func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path string, queryParams url.Values, bodyParams map[string]interface{}, isVersion3 bool, result interface{}, returnHead *http.Header) (err error) { creds, err := c.GetCredentials(ctx) if err != nil { return err @@ -1353,12 +1165,11 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha if err != nil { return err } - + queryString := common.EncodeURLValues("", queryParams) // Version 2 wants query params in the path during signing if !isVersion3 { - path += queryParams + path += queryString } - interim := json.RawMessage{} newRequest := func() (*request.Item, error) { payload := []byte("") @@ -1368,11 +1179,8 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha return nil, err } } - n := strconv.FormatInt(time.Now().Unix(), 10) - message := n + method + path + string(payload) - var hmac []byte hmac, err = crypto.GetHMAC(crypto.HashSHA256, []byte(message), @@ -1380,21 +1188,17 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha if err != nil { return nil, err } - // TODO: Implement JWT authentication once it's supported by all endpoints we care about - headers := make(map[string]string) headers["CB-ACCESS-KEY"] = creds.Key headers["CB-ACCESS-SIGN"] = hex.EncodeToString(hmac) headers["CB-ACCESS-TIMESTAMP"] = n headers["Content-Type"] = "application/json" - headers["CB-VERSION"] = "2024-02-14" - + headers["CB-VERSION"] = "2024-02-27" // Version 3 only wants query params in the path when the request is sent if isVersion3 { - path += queryParams + path += queryString } - return &request.Item{ Method: method, Path: endpoint + path, @@ -1411,11 +1215,9 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha if isVersion3 { rateLim = V3Rate } - err = c.SendPayload(ctx, rateLim, newRequest, request.AuthenticatedRequest) - - // Doing this error handling because the docs indicate that errors can be returned even with a 200 status code, - // and that these errors can be buried in the JSON returned + // Doing this error handling because the docs indicate that errors can be returned even with a 200 status + // code, and that these errors can be buried in the JSON returned if err != nil { return err } @@ -1429,10 +1231,9 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha }{} if err = json.Unmarshal(interim, &singleErrCap); err == nil { if singleErrCap.Message != "" { - errMessage := fmt.Sprintf("message: %s, error type: %s, error details: %s, edit failure reason: %s, preview failure reason: %s, new order failure reason: %s", + return fmt.Errorf("message: %s, error type: %s, error details: %s, edit failure reason: %s, preview failure reason: %s, new order failure reason: %s", singleErrCap.Message, singleErrCap.ErrorType, singleErrCap.ErrorDetails, singleErrCap.EditFailureReason, singleErrCap.PreviewFailureReason, singleErrCap.NewOrderFailureReason) - return errors.New(errMessage) } } manyErrCap := struct { @@ -1446,20 +1247,18 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha }{} err = json.Unmarshal(interim, &manyErrCap) if err == nil { - if len(manyErrCap.Errors) > 0 { - errMessage := "" - for i := range manyErrCap.Errors { - if !manyErrCap.Errors[i].Success || manyErrCap.Errors[i].EditFailureReason != "" || - manyErrCap.Errors[i].PreviewFailureReason != "" { - errMessage += fmt.Sprintf("order id: %s, failure reason: %s, edit failure reason: %s, preview failure reason: %s", - manyErrCap.Errors[i].OrderID, manyErrCap.Errors[i].FailureReason, - manyErrCap.Errors[i].EditFailureReason, manyErrCap.Errors[i].PreviewFailureReason) - } - } - if errMessage != "" { - return errors.New(errMessage) + errMessage := "" + for i := range manyErrCap.Errors { + if !manyErrCap.Errors[i].Success || manyErrCap.Errors[i].EditFailureReason != "" || + manyErrCap.Errors[i].PreviewFailureReason != "" { + errMessage += fmt.Sprintf("order id: %s, failure reason: %s, edit failure reason: %s, preview failure reason: %s", + manyErrCap.Errors[i].OrderID, manyErrCap.Errors[i].FailureReason, + manyErrCap.Errors[i].EditFailureReason, manyErrCap.Errors[i].PreviewFailureReason) } } + if errMessage != "" { + return errors.New(errMessage) + } } if result == nil { return nil @@ -1492,7 +1291,6 @@ func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilde fee = WorstCaseStablePairTakerFee case feeBuilder.IsMaker && !isStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.OfflineTradeFee: fee = WorstCaseMakerFee - fmt.Printf("IsMaker is %v\n", feeBuilder.IsMaker) case !feeBuilder.IsMaker && !isStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.OfflineTradeFee: fee = WorstCaseTakerFee default: @@ -1528,41 +1326,36 @@ func isStablePair(pair currency.Pair) bool { // PrepareDateString encodes a set of parameters indicating start & end dates func (p *Params) prepareDateString(startDate, endDate time.Time, labelStart, labelEnd string) error { err := common.StartEndTimeCheck(startDate, endDate) - - if err == nil { - p.urlVals.Set(labelStart, startDate.Format(time.RFC3339)) - p.urlVals.Set(labelEnd, endDate.Format(time.RFC3339)) - } - if err != nil { - if err.Error() == "start date unset" || err.Error() == "end date unset" { + if errors.Is(err, common.ErrDateUnset) { return nil } + return err } - + p.Values.Set(labelStart, startDate.Format(time.RFC3339)) + p.Values.Set(labelEnd, endDate.Format(time.RFC3339)) return err } // PreparePagination formats pagination information in the way the exchange expects func (p *Params) preparePagination(pag PaginationInp) { if pag.Limit != 0 { - p.urlVals.Set("limit", strconv.FormatInt(int64(pag.Limit), 10)) + p.Values.Set("limit", strconv.FormatInt(int64(pag.Limit), 10)) } if pag.OrderAscend { - p.urlVals.Set("order", "asc") + p.Values.Set("order", "asc") } if pag.StartingAfter != "" { - p.urlVals.Set("starting_after", pag.StartingAfter) + p.Values.Set("starting_after", pag.StartingAfter) } if pag.EndingBefore != "" { - p.urlVals.Set("ending_before", pag.EndingBefore) + p.Values.Set("ending_before", pag.EndingBefore) } } // prepareOrderConfig populates the OrderConfiguration struct func prepareOrderConfig(orderType, side, stopDirection string, amount, limitPrice, stopPrice float64, endTime time.Time, postOnly bool) (OrderConfiguration, error) { var orderConfig OrderConfiguration - switch orderType { case order.Market.String(), order.ImmediateOrCancel.String(): orderConfig.MarketMarketIOC = &MarketMarketIOC{} @@ -1608,14 +1401,15 @@ func prepareOrderConfig(orderType, side, stopDirection string, amount, limitPric return orderConfig, nil } -// prepareMarginType properly formats the margin type for the request -func prepareMarginType(marginType string, req map[string]interface{}) { +// formatMarginType properly formats the margin type for the request +func formatMarginType(marginType string) string { if marginType == "ISOLATED" || marginType == "CROSS" { - req["margin_type"] = marginType + return marginType } if marginType == "MULTI" { - req["margin_type"] = "CROSS" + return "CROSS" } + return "" } // String implements the stringer interface @@ -1647,6 +1441,6 @@ func (t *UnixTimestamp) String() string { } // Time returns the time.Time representation of the UnixTimestamp -func (t UnixTimestamp) Time() time.Time { - return time.Time(t) +func (t *UnixTimestamp) Time() time.Time { + return time.Time(*t) } diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 063aac9547a..6c24075651a 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -189,7 +189,7 @@ func TestGetProductBook(t *testing.T) { func TestGetAllProducts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) testPairs := []string{testPair.String(), "ETH-USD"} - resp, err := c.GetAllProducts(context.Background(), 30000, 0, "SPOT", "PERPETUAL", "STATUS_ALL", + resp, err := c.GetAllProducts(context.Background(), 30000, 1, "SPOT", "PERPETUAL", "STATUS_ALL", testPairs) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -222,7 +222,7 @@ func TestGetTicker(t *testing.T) { _, err := c.GetTicker(context.Background(), "", 1, time.Time{}, time.Time{}) assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetTicker(context.Background(), testPair.String(), 5, time.Time{}, time.Time{}) + resp, err := c.GetTicker(context.Background(), testPair.String(), 5, time.Now().Add(-time.Minute*5), time.Now()) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -347,7 +347,8 @@ func TestGetOrderByID(t *testing.T) { if len(ordID.Orders) == 0 { t.Skip(skipInsufficientOrders) } - resp, err := c.GetOrderByID(context.Background(), ordID.Orders[0].OrderID, ordID.Orders[0].ClientOID, "") + resp, err := c.GetOrderByID(context.Background(), ordID.Orders[0].OrderID, ordID.Orders[0].ClientOID, + testFiat.String()) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -392,10 +393,10 @@ func TestMovePortfolioFunds(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) portID, err := c.GetAllPortfolios(context.Background(), "") assert.NoError(t, err) - if len(portID.Portfolios) < 2 { + if len(portID) < 2 { t.Skip(skipInsufficientPortfolios) } - _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID.Portfolios[0].UUID, portID.Portfolios[1].UUID, + _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID[0].UUID, portID[1].UUID, testAmount) if err != nil && err.Error() != errPortTransferInsufFunds { t.Error(err) @@ -408,13 +409,13 @@ func TestGetPortfolioByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) portID, err := c.GetAllPortfolios(context.Background(), "") assert.NoError(t, err) - if len(portID.Portfolios) == 0 { + if len(portID) == 0 { t.Fatal(errExpectedNonEmpty) } - resp, err := c.GetPortfolioByID(context.Background(), portID.Portfolios[0].UUID) + resp, err := c.GetPortfolioByID(context.Background(), portID[0].UUID) assert.NoError(t, err) - if resp.Breakdown.Portfolio != portID.Portfolios[0] { - t.Errorf(errExpectMismatch, resp.Breakdown.Portfolio, portID.Portfolios[0]) + if resp.Breakdown.Portfolio != portID[0] { + t.Errorf(errExpectMismatch, resp.Breakdown.Portfolio, portID[0]) } } @@ -446,7 +447,8 @@ func TestGetFuturesBalanceSummary(t *testing.T) { func TestGetAllFuturesPositions(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - testGetNoArgs(t, c.GetAllFuturesPositions) + _, err := c.GetAllFuturesPositions(context.Background()) + assert.NoError(t, err) } func TestGetFuturesPositionByID(t *testing.T) { @@ -462,9 +464,9 @@ func TestScheduleFuturesSweep(t *testing.T) { curSweeps, err := c.ListFuturesSweeps(context.Background()) assert.NoError(t, err) preCancel := false - if len(curSweeps.Sweeps) > 0 { - for i := range curSweeps.Sweeps { - if curSweeps.Sweeps[i].Status == "PENDING" { + if len(curSweeps) > 0 { + for i := range curSweeps { + if curSweeps[i].Status == "PENDING" { preCancel = true } } @@ -490,9 +492,9 @@ func TestCancelPendingFuturesSweep(t *testing.T) { curSweeps, err := c.ListFuturesSweeps(context.Background()) assert.NoError(t, err) partialSkip := false - if len(curSweeps.Sweeps) > 0 { - for i := range curSweeps.Sweeps { - if curSweeps.Sweeps[i].Status == "PENDING" { + if len(curSweeps) > 0 { + for i := range curSweeps { + if curSweeps[i].Status == "PENDING" { partialSkip = true } } @@ -551,8 +553,8 @@ func TestGetTransactionSummary(t *testing.T) { _, err := c.GetTransactionSummary(context.Background(), time.Unix(2, 2), time.Unix(1, 1), "", "", "") assert.ErrorIs(t, err, common.ErrStartAfterEnd) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetTransactionSummary(context.Background(), time.Unix(1, 1), time.Now(), "", asset.Spot.Upper(), - "UNKNOWN_CONTRACT_EXPIRY_TYPE") + resp, err := c.GetTransactionSummary(context.Background(), time.Unix(1, 1), time.Now(), testFiat.String(), + asset.Spot.Upper(), "UNKNOWN_CONTRACT_EXPIRY_TYPE") assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -901,12 +903,6 @@ func TestGetPrice(t *testing.T) { resp, err := c.GetPrice(context.Background(), testPair.String(), asset.Spot.String()) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) - resp, err = c.GetPrice(context.Background(), testPair.String(), "buy") - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) - resp, err = c.GetPrice(context.Background(), testPair.String(), "sell") - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetV2Time(t *testing.T) { @@ -922,16 +918,16 @@ func TestSendHTTPRequest(t *testing.T) { func TestSendAuthenticatedHTTPRequest(t *testing.T) { fc := &CoinbasePro{} - err := fc.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", "", nil, false, nil, nil) + err := fc.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", nil, nil, false, nil, nil) assert.ErrorIs(t, err, exchange.ErrCredentialsAreEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", "", nil, false, nil, nil) + err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", nil, nil, false, nil, nil) if err.Error() != errNoEndpointPathEdgeCase3 { t.Errorf(errExpectMismatch, err, errNoEndpointPathEdgeCase3) } ch := make(chan struct{}) body := map[string]interface{}{"Unmarshalable": ch} - err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.RestSpot, "", "", "", body, false, nil, nil) + err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.RestSpot, "", "", nil, body, false, nil, nil) if err.Error() != errJSONUnsupportedChan { t.Errorf(errExpectMismatch, err, errJSONUnsupportedChan) } @@ -1716,12 +1712,12 @@ func portfolioIDFromName(t *testing.T, targetName string) string { if err != nil { t.Error(err) } - if len(getResp.Portfolios) == 0 { + if len(getResp) == 0 { t.Fatal(errExpectedNonEmpty) } - for i := range getResp.Portfolios { - if getResp.Portfolios[i].Name == targetName { - targetID = getResp.Portfolios[i].UUID + for i := range getResp { + if getResp[i].Name == targetName { + targetID = getResp[i].UUID break } } @@ -1736,13 +1732,13 @@ func getINTXPortfolio(t *testing.T) string { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetAllPortfolios(context.Background(), "") assert.NoError(t, err) - if len(resp.Portfolios) == 0 { + if len(resp) == 0 { t.Skip(skipInsufficientPortfolios) } var targetID string - for i := range resp.Portfolios { - if resp.Portfolios[i].Type == "INTX" { - targetID = resp.Portfolios[i].UUID + for i := range resp { + if resp[i].Type == "INTX" { + targetID = resp[i].UUID break } } @@ -1776,7 +1772,7 @@ func convertTestHelper(t *testing.T) (fromAccID, toAccID string) { return fromAccID, toAccID } -func transferTestHelper(t *testing.T, wallets GetAllWalletsResponse) (srcWalletID, tarWalletID string) { +func transferTestHelper(t *testing.T, wallets *GetAllWalletsResponse) (srcWalletID, tarWalletID string) { t.Helper() var hasValidFunds bool for i := range wallets.Data { @@ -1846,8 +1842,8 @@ func withdrawFiatFundsHelper(t *testing.T, fn withdrawFiatFunc) { } type getNoArgsResp interface { - AllFuturesPositions | ServerTimeV3 | *UserResponse | AuthResponse | GetFiatCurrenciesResp | - GetCryptocurrenciesResp | ServerTimeV2 + *ServerTimeV3 | *UserResponse | *AuthResponse | []FiatData | + []CryptoData | *ServerTimeV2 } type getNoArgsAssertNotEmpty[G getNoArgsResp] func(context.Context) (G, error) @@ -1859,7 +1855,7 @@ func testGetNoArgs[G getNoArgsResp](t *testing.T, f getNoArgsAssertNotEmpty[G]) assert.NotEmpty(t, resp, errExpectedNonEmpty) } -type genConvertTestFunc func(context.Context, string, string, string) (ConvertResponse, error) +type genConvertTestFunc func(context.Context, string, string, string) (*ConvertResponse, error) func convertTestShared(t *testing.T, f genConvertTestFunc) { t.Helper() diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index ae9d5af33b9..988a91c69bc 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -61,7 +61,7 @@ type AllAccountsResponse struct { // Params is used within functions to make the setting of parameters easier type Params struct { - urlVals url.Values + url.Values } // OneAccountResponse is a temporary struct used for unmarshalling in GetAccountByID @@ -84,8 +84,8 @@ type ProductBook struct { Time time.Time `json:"time"` } -// BestBidAsk holds the best bid and ask prices for a variety of products, returned by -// GetBestBidAsk +// BestBidAsk holds the best bid and ask prices for a variety of products, used for +// unmarshalling in GetBestBidAsk type BestBidAsk struct { Pricebooks []ProductBook `json:"pricebooks"` } @@ -168,16 +168,20 @@ type AllProducts struct { // the exchange, used in the types History and WebsocketCandle type UnixTimestamp time.Time -// History holds historic rate information, returned by GetHistoricRates +// CandleStruct holds historic trade information, used as a sub-struct in History, +// and returned by GetHistoricRates +type CandleStruct struct { + Start UnixTimestamp `json:"start"` + Low float64 `json:"low,string"` + High float64 `json:"high,string"` + Open float64 `json:"open,string"` + Close float64 `json:"close,string"` + Volume float64 `json:"volume,string"` +} + +// History holds historic rate information, used for unmarshalling in GetHistoricRates type History struct { - Candles []struct { - Start UnixTimestamp `json:"start"` - Low float64 `json:"low,string"` - High float64 `json:"high,string"` - Open float64 `json:"open,string"` - Close float64 `json:"close,string"` - Volume float64 `json:"volume,string"` - } `json:"candles"` + Candles []CandleStruct `json:"candles"` } // Ticker holds basic ticker information, returned by GetTicker @@ -258,14 +262,18 @@ type PlaceOrderResp struct { OrderConfiguration OrderConfiguration `json:"order_configuration"` } -// CancelOrderResp contains information on attempted order cancellations, returned by -// CancelOrders +// OrderCancelDetail contains information on attempted order cancellations, used as a +// sub-struct by CancelOrdersResp, and returned by CancelOrders +type OrderCancelDetail struct { + Success bool `json:"success"` + FailureReason string `json:"failure_reason"` + OrderID string `json:"order_id"` +} + +// CancelOrderResp contains information on attempted order cancellations, used for unmarshalling +// by CancelOrders type CancelOrderResp struct { - Results []struct { - Success bool `json:"success"` - FailureReason string `json:"failure_reason"` - OrderID string `json:"order_id"` - } `json:"results"` + Results []OrderCancelDetail `json:"results"` } // EditOrderPreviewResp contains information on the effects of editing an order, @@ -371,8 +379,8 @@ type SimplePortfolioData struct { Deleted bool `json:"deleted"` } -// AllPortfolioResponse contains a brief overview of the user's portfolios, returned by -// GetAllPortfolios +// AllPortfolioResponse contains a brief overview of the user's portfolios, used in unmarshalling +// for GetAllPortfolios type AllPortfolioResponse struct { Portfolios []SimplePortfolioData `json:"portfolios"` } @@ -501,28 +509,32 @@ type FuturesPosition struct { DailyRealizedPNL float64 `json:"daily_realized_pnl,string"` } -// AllFuturesPositions contains information on all futures positions, returned by -// GetAllFuturesPositions +// AllFuturesPositions contains information on all futures positions, used in unmarshalling +// by GetAllFuturesPositions type AllFuturesPositions struct { Positions []FuturesPosition `json:"positions"` } -// SuccessBool is returned by some endpoints to indicate a failure or a success. Returned -// by EditOrder, ScheduleFuturesSweep, and CancelPendingFuturesSweep +// SuccessBool is returned by some endpoints to indicate a failure or a success. Used in +// unmarshalling by EditOrder, ScheduleFuturesSweep, and CancelPendingFuturesSweep type SuccessBool struct { Success bool `json:"success"` } +// SweepData contains information on pending and processing sweep requests, used as a +// sub-struct in ListFuturesSweepsResponse, and returned by ListFuturesSweeps +type SweepData struct { + ID string `json:"id"` + RequestedAmount ValCur `json:"requested_amount"` + ShouldSweepAll bool `json:"should_sweep_all"` + Status string `json:"status"` + ScheduledTime time.Time `json:"scheduled_time"` +} + // ListFuturesSweepsResponse contains information on pending and processing sweep -// requests. Returned by ListFuturesSweeps +// requests. Used in unmarshalling by ListFuturesSweeps type ListFuturesSweepsResponse struct { - Sweeps []struct { - ID string `json:"id"` - RequestedAmount ValCur `json:"requested_amount"` - ShouldSweepAll bool `json:"should_sweep_all"` - Status string `json:"status"` - ScheduledTime time.Time `json:"scheduled_time"` - } `json:"sweeps"` + Sweeps []SweepData `json:"sweeps"` } // PerpetualsPortfolioSummary contains information on perpetuals portfolio balances, used as @@ -1116,29 +1128,37 @@ type GenPaymentMethodResp struct { Data PaymentMethodData `json:"data"` } -// GetFiatCurrenciesResp holds information on fiat currencies. Returned by -// GetFiatCurrencies +// FiatData holds information on fiat currencies. Used as a sub-struct in +// GetFiatCurrenciesResp, and returned by GetFiatCurrencies +type FiatData struct { + ID string `json:"id"` + Name string `json:"name"` + MinSize float64 `json:"min_size,string"` +} + +// GetFiatCurrenciesResp holds information on fiat currencies. Used for +// unmarshalling in GetFiatCurrencies type GetFiatCurrenciesResp struct { - Data []struct { - ID string `json:"id"` - Name string `json:"name"` - MinSize float64 `json:"min_size,string"` - } + Data []FiatData `json:"data"` +} + +// CryptoData holds information on cryptocurrencies. Used as a sub-struct in +// GetCryptocurrenciesResp, and returned by GetCryptocurrencies +type CryptoData struct { + Code string `json:"code"` + Name string `json:"name"` + Color string `json:"color"` + SortIndex uint16 `json:"sort_index"` + Exponent uint8 `json:"exponent"` + Type string `json:"type"` + AddressRegex string `json:"address_regex"` + AssetID string `json:"asset_id"` } -// GetCryptocurrenciesResp holds information on cryptocurrencies. Returned by -// GetCryptocurrencies +// GetCryptocurrenciesResp holds information on cryptocurrencies. Used for +// unmarshalling in GetCryptocurrencies type GetCryptocurrenciesResp struct { - Data []struct { - Code string `json:"code"` - Name string `json:"name"` - Color string `json:"color"` - SortIndex uint16 `json:"sort_index"` - Exponent uint8 `json:"exponent"` - Type string `json:"type"` - AddressRegex string `json:"address_regex"` - AssetID string `json:"asset_id"` - } + Data []CryptoData `json:"data"` } // GetExchangeRatesResp holds information on exchange rates. Returned by GetExchangeRates diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index d31b90b6574..fec4cd06a3c 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -44,7 +44,6 @@ func (c *CoinbasePro) WsConnect() error { if err != nil { return err } - c.Websocket.Wg.Add(1) go c.wsReadData() return nil @@ -53,9 +52,7 @@ func (c *CoinbasePro) WsConnect() error { // wsReadData receives and passes on websocket messages for processing func (c *CoinbasePro) wsReadData() { defer c.Websocket.Wg.Done() - var seqCount uint64 - for { resp := c.Websocket.Conn.ReadMessage() if resp.Raw == nil { @@ -83,66 +80,52 @@ 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 - seqData, _, _, err := jsonparser.Get(respRaw, "sequence_num") if err != nil { return warnString, err } - seqNum, err := strconv.ParseUint(string(seqData), 10, 64) if err != nil { return warnString, err } - if seqNum != seqCount { warnString = fmt.Sprintf(warnSequenceIssue, seqNum, seqCount) } - channelRaw, _, _, err := jsonparser.Get(respRaw, "channel") if err != nil { return warnString, err } - channel := string(channelRaw) - if channel == "subscriptions" || channel == "heartbeats" { return warnString, nil } - data, _, _, err := jsonparser.Get(respRaw, "events") if err != nil { return warnString, err } - switch channel { case "status": wsStatus := []WebsocketProductHolder{} - err = json.Unmarshal(data, &wsStatus) if err != nil { 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) if err != nil { return warnString, err } - sliToSend := []ticker.Price{} - var timestamp time.Time timestamp, err = getTimestamp(respRaw) if err != nil { return warnString, err } - for i := range wsTicker { for j := range wsTicker[i].Tickers { sliToSend = append(sliToSend, ticker.Price{ @@ -160,20 +143,16 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err c.Websocket.DataHandler <- sliToSend case "candles": wsCandles := []WebsocketCandleHolder{} - err = json.Unmarshal(data, &wsCandles) if err != nil { return warnString, err } - sliToSend := []stream.KlineData{} - var timestamp time.Time timestamp, err = getTimestamp(respRaw) if err != nil { return warnString, err } - for i := range wsCandles { for j := range wsCandles[i].Candles { sliToSend = append(sliToSend, stream.KlineData{ @@ -193,14 +172,11 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err c.Websocket.DataHandler <- sliToSend case "market_trades": wsTrades := []WebsocketMarketTradeHolder{} - err = json.Unmarshal(data, &wsTrades) if err != nil { return warnString, err } - sliToSend := []trade.Data{} - for i := range wsTrades { for j := range wsTrades[i].Trades { sliToSend = append(sliToSend, trade.Data{ @@ -222,12 +198,10 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err if err != nil { return warnString, err } - timestamp, err := getTimestamp(respRaw) if err != nil { return warnString, err } - for i := range wsL2 { switch wsL2[i].Type { case "snapshot": @@ -247,7 +221,6 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err if err != nil { return warnString, err } - sliToSend := []order.Detail{} for i := range wsUser { for j := range wsUser[i].Orders { @@ -256,19 +229,16 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err if err != nil { return warnString, err } - var oSide order.Side oSide, err = order.StringToOrderSide(wsUser[i].Orders[j].OrderSide) if err != nil { return warnString, err } - var oStatus order.Status oStatus, err = statusToStandardStatus(wsUser[i].Orders[j].Status) if err != nil { return warnString, err } - sliToSend = append(sliToSend, order.Detail{ Price: wsUser[i].Orders[j].AveragePrice, Amount: wsUser[i].Orders[j].CumulativeQuantity + wsUser[i].Orders[j].LeavesQuantity, @@ -288,7 +258,6 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err } } c.Websocket.DataHandler <- sliToSend - default: return warnString, errChannelNameUnknown } @@ -298,11 +267,9 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err // ProcessSnapshot processes the initial orderbook snap shot func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookDataHolder, timestamp time.Time) error { bids, asks, err := processBidAskArray(snapshot) - if err != nil { return err } - return c.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{ Bids: bids, Asks: asks, @@ -317,11 +284,9 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookDataHolder, ti // ProcessUpdate updates the orderbook local cache func (c *CoinbasePro) ProcessUpdate(update *WebsocketOrderbookDataHolder, timestamp time.Time) error { bids, asks, err := processBidAskArray(update) - if err != nil { return err } - obU := orderbook.Update{ Bids: bids, Asks: asks, @@ -329,7 +294,6 @@ func (c *CoinbasePro) ProcessUpdate(update *WebsocketOrderbookDataHolder, timest UpdateTime: timestamp, Asset: asset.Spot, } - return c.Websocket.Orderbook.Update(&obU) } @@ -370,7 +334,6 @@ func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]subscription.Subscriptio // Subscribe sends a websocket message to receive data from the channel func (c *CoinbasePro) Subscribe(channelsToSubscribe []subscription.Subscription) error { chanKeys := make(map[string]currency.Pairs) - for i := range channelsToSubscribe { chanKeys[channelsToSubscribe[i].Channel] = chanKeys[channelsToSubscribe[i].Channel].Add(channelsToSubscribe[i].Pair) @@ -382,7 +345,6 @@ func (c *CoinbasePro) Subscribe(channelsToSubscribe []subscription.Subscription) } time.Sleep(time.Millisecond * 10) } - c.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) return nil } @@ -390,12 +352,10 @@ func (c *CoinbasePro) Subscribe(channelsToSubscribe []subscription.Subscription) // Unsubscribe sends a websocket message to stop receiving data from the channel func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { chanKeys := make(map[string]currency.Pairs) - for i := range channelsToUnsubscribe { chanKeys[channelsToUnsubscribe[i].Channel] = chanKeys[channelsToUnsubscribe[i].Channel].Add(channelsToUnsubscribe[i].Pair) } - for s := range chanKeys { err := c.sendRequest("unsubscribe", s, chanKeys[s]) if err != nil { @@ -403,7 +363,6 @@ func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe []subscription.Subscript } time.Sleep(time.Millisecond * 10) } - c.Websocket.RemoveSubscriptions(channelsToUnsubscribe...) return nil } @@ -415,36 +374,29 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, error) { if c.jwtLastRegen.Add(time.Minute*2).After(time.Now()) && uri != "" { return c.jwt, nil } - creds, err := c.GetCredentials(ctx) if err != nil { return "", err } - block, _ := pem.Decode([]byte(creds.Secret)) if block == nil { return "", errCantDecodePrivKey } - key, err := x509.ParseECPrivateKey(block.Bytes) if err != nil { return "", err } - nonce, err := common.GenerateRandomString(64, "1234567890ABCDEF") if err != nil { return "", err } - head := map[string]interface{}{"kid": creds.ClientID, "typ": "JWT", "alg": "ES256", "nonce": nonce} headJSON, err := json.Marshal(head) if err != nil { return "", err } headEncode := base64URLEncode(headJSON) - c.jwtLastRegen = time.Now() - body := map[string]interface{}{"iss": "coinbase-cloud", "nbf": time.Now().Unix(), "exp": time.Now().Add(time.Minute * 2).Unix(), "sub": creds.ClientID, "aud": "retail_rest_api_proxy"} if uri != "" { @@ -455,15 +407,12 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, error) { return "", err } bodyEncode := base64URLEncode(bodyJSON) - hash := sha256.Sum256([]byte(headEncode + "." + bodyEncode)) - sig, err := ecdsa.SignASN1(rand.Reader, key, hash[:]) if err != nil { return "", err } sigEncode := base64URLEncode(sig) - return headEncode + "." + bodyEncode + "." + sigEncode, nil } @@ -487,21 +436,16 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.P if err != nil { return err } - n := strconv.FormatInt(time.Now().Unix(), 10) - message := n + channel + productIDs.Join() - hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(message), []byte(creds.Secret)) if err != nil { return err } - // TODO: Implement JWT authentication once our REST implementation moves to it, or if there's // an exchange-wide reform to enable multiple sets of authentication credentials - req := WebsocketRequest{ Type: msgType, ProductIDs: productIDs.Strings(), @@ -510,7 +454,6 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.P Key: creds.Key, Timestamp: n, } - if err != nil { return err } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 385aa3768ea..18ba68fcc54 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -2,14 +2,13 @@ package coinbasepro import ( "context" - "encoding/hex" "fmt" + "math" "strconv" "time" "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -38,19 +37,16 @@ func (c *CoinbasePro) GetDefaultConfig(ctx context.Context) (*config.Exchange, e if err != nil { return nil, err } - err = c.SetupDefaults(exchCfg) if err != nil { return nil, err } - if c.Features.Supports.RESTCapabilities.AutoPairUpdates && c.Base.API.AuthenticatedSupport { err = c.UpdateTradablePairs(ctx, true) if err != nil { return nil, err } } - return exchCfg, nil } @@ -61,14 +57,12 @@ func (c *CoinbasePro) SetDefaults() { c.API.CredentialsValidator.RequiresKey = true c.API.CredentialsValidator.RequiresSecret = true c.API.CredentialsValidator.RequiresBase64DecodeSecret = false - requestFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} configFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} err := c.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot, asset.Futures) if err != nil { log.Errorln(log.ExchangeSys, err) } - c.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ REST: true, @@ -131,7 +125,6 @@ func (c *CoinbasePro) SetDefaults() { }, }, } - c.Requester, err = request.New(c.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), request.WithLimiter(SetRateLimit())) @@ -167,12 +160,10 @@ func (c *CoinbasePro) Setup(exch *config.Exchange) error { if err != nil { return err } - wsRunningURL, err := c.API.Endpoints.GetURL(exchange.WebsocketSpot) if err != nil { return err } - err = c.Websocket.Setup(&stream.WebsocketSetup{ ExchangeConfig: exch, DefaultURL: coinbaseproWebsocketURL, @@ -189,7 +180,6 @@ func (c *CoinbasePro) Setup(exch *config.Exchange) error { if err != nil { return err } - return c.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, @@ -198,7 +188,7 @@ func (c *CoinbasePro) Setup(exch *config.Exchange) error { // FetchTradablePairs returns a list of the exchanges tradable pairs func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.Pairs, error) { - var products AllProducts + var products *AllProducts var err error switch a { case asset.Spot: @@ -208,11 +198,9 @@ func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, a asset.Item) (cur default: err = asset.ErrNotSupported } - if err != nil { return nil, err } - pairs := make([]currency.Pair, 0, len(products.Products)) for x := range products.Products { if products.Products[x].TradingDisabled { @@ -231,7 +219,7 @@ func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, a asset.Item) (cur // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (c *CoinbasePro) UpdateTradablePairs(ctx context.Context, forceUpdate bool) error { - assets := c.GetAssetTypes(true) + assets := c.GetAssetTypes(false) for i := range assets { pairs, err := c.FetchTradablePairs(ctx, assets[i]) if err != nil { @@ -254,10 +242,9 @@ func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Ite done bool err error cursor string - accountResp AllAccountsResponse + accountResp *AllAccountsResponse ) response.Exchange = c.Name - for !done { accountResp, err = c.GetAllAccounts(ctx, 250, cursor) if err != nil { @@ -267,7 +254,6 @@ func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Ite done = !accountResp.HasNext cursor = accountResp.Cursor } - accountCurrencies := make(map[string][]account.Balance) for i := range accountBalance { profileID := accountBalance[i].UUID @@ -279,14 +265,11 @@ func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Ite Free: accountBalance[i].AvailableBalance.Value - accountBalance[i].Hold.Value, AvailableWithoutBorrow: accountBalance[i].AvailableBalance.Value, - Borrowed: 0, }) } - if response.Accounts, err = account.CollectBalances(accountCurrencies, assetType); err != nil { return account.Holdings{}, err } - creds, err := c.GetCredentials(ctx) if err != nil { return account.Holdings{}, err @@ -295,7 +278,6 @@ func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Ite if err != nil { return account.Holdings{}, err } - return response, nil } @@ -314,8 +296,10 @@ func (c *CoinbasePro) FetchAccountInfo(ctx context.Context, assetType asset.Item // UpdateTickers updates all currency pairs of a given asset type func (c *CoinbasePro) UpdateTickers(ctx context.Context, assetType asset.Item) error { - products, _ := c.GetEnabledPairs(assetType) - + products, err := c.GetEnabledPairs(assetType) + if err != nil { + return err + } for x := range products { tick, err := c.GetTicker(ctx, products[x].String(), 1, time.Time{}, time.Time{}) if err != nil { @@ -350,17 +334,14 @@ func (c *CoinbasePro) UpdateTicker(ctx context.Context, p currency.Pair, a asset if err != nil { return nil, err } - tick, err := c.GetTicker(ctx, fPair.String(), 1, time.Time{}, time.Time{}) if err != nil { return nil, err } - var last float64 if len(tick.Trades) != 0 { last = tick.Trades[0].Price } - tickerPrice := &ticker.Price{ Last: last, Bid: tick.BestBid.Float64(), @@ -368,12 +349,10 @@ func (c *CoinbasePro) UpdateTicker(ctx context.Context, p currency.Pair, a asset Pair: p, ExchangeName: c.Name, AssetType: a} - err = ticker.ProcessTicker(tickerPrice) if err != nil { return tickerPrice, err } - return ticker.GetTicker(c.Name, p, a) } @@ -426,12 +405,10 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse if err != nil { return book, err } - orderbookNew, err := c.GetProductBook(ctx, fPair.String(), 1000) if err != nil { return book, err } - book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { book.Bids[x] = orderbook.Item{ @@ -439,7 +416,6 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse Price: orderbookNew.Bids[x].Price, } } - book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { book.Asks[x] = orderbook.Item{ @@ -458,16 +434,13 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse // withdrawals func (c *CoinbasePro) GetAccountFundingHistory(ctx context.Context) ([]exchange.FundingHistory, error) { wallIDs, err := c.GetAllWallets(ctx, PaginationInp{}) - if err != nil { return nil, err } if len(wallIDs.Data) == 0 { return nil, errNoWalletsReturned } - var accHistory []DeposWithdrData - for i := range wallIDs.Data { tempAccHist, err := c.GetAllFiatTransfers(ctx, wallIDs.Data[i].ID, PaginationInp{}, FiatDeposit) if err != nil { @@ -480,9 +453,7 @@ func (c *CoinbasePro) GetAccountFundingHistory(ctx context.Context) ([]exchange. } accHistory = append(accHistory, tempAccHist.Data...) } - var cryptoHistory []TransactionData - for i := range wallIDs.Data { tempCryptoHist, err := c.GetAllTransactions(ctx, wallIDs.Data[i].ID, PaginationInp{}) if err != nil { @@ -494,37 +465,29 @@ func (c *CoinbasePro) GetAccountFundingHistory(ctx context.Context) ([]exchange. } } } - fundingData := c.processFundingData(accHistory, cryptoHistory) - return fundingData, nil } // GetWithdrawalsHistory returns previous withdrawals data func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Code, _ asset.Item) ([]exchange.WithdrawalHistory, error) { tempWallIDs, err := c.GetAllWallets(ctx, PaginationInp{}) - if err != nil { return nil, err } if len(tempWallIDs.Data) == 0 { return nil, errNoWalletsReturned } - var wallIDs []string - for i := range tempWallIDs.Data { if tempWallIDs.Data[i].Currency.Code == cur.String() { wallIDs = append(wallIDs, tempWallIDs.Data[i].ID) } } - if len(wallIDs) == 0 { return nil, errNoMatchingWallets } - var accHistory []DeposWithdrData - for i := range wallIDs { tempAccHist, err := c.GetAllFiatTransfers(ctx, wallIDs[i], PaginationInp{}, FiatWithdrawal) if err != nil { @@ -532,9 +495,7 @@ func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Co } accHistory = append(accHistory, tempAccHist.Data...) } - var cryptoHistory []TransactionData - for i := range wallIDs { tempCryptoHist, err := c.GetAllTransactions(ctx, wallIDs[i], PaginationInp{}) if err != nil { @@ -546,11 +507,8 @@ func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Co } } } - tempFundingData := c.processFundingData(accHistory, cryptoHistory) - fundingData := make([]exchange.WithdrawalHistory, len(tempFundingData)) - for i := range tempFundingData { fundingData[i] = exchange.WithdrawalHistory{ Status: tempFundingData[i].Status, @@ -567,7 +525,6 @@ func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Co BankTo: tempFundingData[i].BankTo, } } - return fundingData, nil } @@ -590,14 +547,11 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. if err != nil { return nil, err } - fPair, err := c.FormatExchangeCurrency(s.Pair, s.AssetType) if err != nil { return nil, err } - var stopDir string - if s.Type == order.StopLimit { switch s.StopDirection { case order.StopUp: @@ -606,25 +560,19 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. stopDir = "STOP_DIRECTION_STOP_DOWN" } } - amount := s.Amount - if (s.Type == order.Market || s.Type == order.ImmediateOrCancel) && s.Side == order.Buy { amount = s.QuoteAmount } - resp, err := c.PlaceOrder(ctx, s.ClientOrderID, fPair.String(), s.Side.String(), stopDir, s.Type.String(), "", s.MarginType.Upper(), "", amount, s.Price, s.TriggerPrice, s.Leverage, s.PostOnly, s.EndTime) - if err != nil { return nil, err } - subResp, err := s.DeriveSubmitResponse(resp.OrderID) if err != nil { return nil, err } - if s.RetrieveFees { time.Sleep(s.RetrieveFeeDelay) feeResp, err := c.GetOrderByID(ctx, resp.OrderID, s.ClientOrderID, "") @@ -650,7 +598,7 @@ func (c *CoinbasePro) ModifyOrder(ctx context.Context, m *order.Modify) (*order. if err != nil { return nil, err } - if !success.Success { + if !success { return nil, errOrderModFailNoRet } @@ -699,13 +647,11 @@ func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (o return resp, err } ordStatus := []string{"OPEN"} - ordIDs, err := c.iterativeGetAllOrders(ctx, can.Pair.String(), "", "", "", ordStatus, 1000, time.Time{}, time.Time{}) if err != nil { return resp, err } - if len(ordStatus) == 0 { return resp, errNoMatchingOrders } @@ -713,15 +659,12 @@ func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (o for i := range ordIDs { orders[i] = order.Cancel{OrderID: ordIDs[i].OrderID} } - batchResp, count, err := c.cancelOrdersReturnMapAndCount(ctx, orders) if err != nil { return resp, err } - resp.Status = batchResp resp.Count = count - return resp, nil } @@ -731,12 +674,10 @@ func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair cur if err != nil { return nil, err } - response, err := c.getOrderRespToOrderDetail(genOrderDetail, pair, assetItem) if err != nil { return nil, err } - fillData, err := c.GetFills(ctx, orderID, "", "", time.Time{}, time.Now(), 2<<15-1) if err != nil { return nil, err @@ -770,7 +711,6 @@ func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair cur Total: fillData.Fills[i].Price * fillData.Fills[i].Size, } } - return response, nil } @@ -781,12 +721,10 @@ func (c *CoinbasePro) GetDepositAddress(ctx context.Context, cryptocurrency curr return nil, err } var targetWalletID string - if len(allWalResp.Data) != 0 { - for i := range allWalResp.Data { - if allWalResp.Data[i].Currency.Code == cryptocurrency.String() { - targetWalletID = allWalResp.Data[i].ID - break - } + for i := range allWalResp.Data { + if allWalResp.Data[i].Currency.Code == cryptocurrency.String() { + targetWalletID = allWalResp.Data[i].ID + break } } if targetWalletID == "" { @@ -812,40 +750,16 @@ func (c *CoinbasePro) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawR if withdrawRequest.WalletID == "" { return nil, errWalletIDEmpty } - - creds, err := c.GetCredentials(ctx) - if err != nil { - return nil, err - } - - message := fmt.Sprintf("%+v", withdrawRequest) - - hmac, err := crypto.GetHMAC(crypto.HashSHA256, - []byte(message), - []byte(creds.Secret)) - if err != nil { - return nil, err - } - - message = hex.EncodeToString(hmac) - - var tocut uint8 - if len(message) > 100 { - tocut = 100 - } else { - tocut = uint8(len(message)) - } - - message = message[:tocut] - + t := time.Now().UnixNano() + u := math.Float64bits(withdrawRequest.Amount) + t = t ^ int64(u) + message := strconv.FormatInt(t, 10) resp, err := c.SendMoney(ctx, "send", withdrawRequest.WalletID, withdrawRequest.Crypto.Address, withdrawRequest.Currency.String(), withdrawRequest.Description, message, "", withdrawRequest.Crypto.AddressTag, withdrawRequest.Amount, false, false) - if err != nil { return nil, err } - return &withdraw.ExchangeResponse{Name: resp.Data.Network.Name, ID: resp.Data.ID, Status: resp.Data.Status}, nil } @@ -858,12 +772,10 @@ func (c *CoinbasePro) WithdrawFiatFunds(ctx context.Context, withdrawRequest *wi if withdrawRequest.WalletID == "" { return nil, errWalletIDEmpty } - paymentMethods, err := c.GetAllPaymentMethods(ctx, PaginationInp{}) if err != nil { return nil, err } - selectedWithdrawalMethod := PaymentMethodData{} for i := range paymentMethods.Data { if withdrawRequest.Fiat.Bank.BankName == paymentMethods.Data[i].Name { @@ -874,14 +786,11 @@ func (c *CoinbasePro) WithdrawFiatFunds(ctx context.Context, withdrawRequest *wi if selectedWithdrawalMethod.ID == "" { return nil, fmt.Errorf(errPayMethodNotFound, withdrawRequest.Fiat.Bank.BankName) } - resp, err := c.FiatTransfer(ctx, withdrawRequest.WalletID, withdrawRequest.Currency.String(), selectedWithdrawalMethod.ID, withdrawRequest.Amount, true, FiatWithdrawal) - if err != nil { return nil, err } - return &withdraw.ExchangeResponse{ Name: selectedWithdrawalMethod.Name, ID: resp.Data.ID, @@ -935,7 +844,6 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder respOrders = append(respOrders, interResp...) } } - orders := make([]order.Detail, len(respOrders)) for i := range respOrders { orderRec, err := c.getOrderRespToOrderDetail(&respOrders[i], req.Pairs[i], asset.Spot) @@ -954,9 +862,7 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder if err != nil { return nil, err } - var p []string - if len(req.Pairs) == 0 { p = make([]string, 1) } else { @@ -969,11 +875,9 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder p[i] = req.Pairs[i].String() } } - closedStatuses := []string{"FILLED", "CANCELLED", "EXPIRED", "FAILED"} openStatus := []string{"OPEN"} var ord []GetOrderResponse - for i := range p { interOrd, err := c.iterativeGetAllOrders(ctx, p[i], req.Type.String(), req.Side.String(), req.AssetType.Upper(), closedStatuses, 2<<30-1, req.StartTime, req.EndTime) @@ -988,9 +892,7 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder } ord = append(ord, interOrd...) } - orders := make([]order.Detail, len(ord)) - for i := range ord { singleOrder, err := c.getOrderRespToOrderDetail(&ord[i], req.Pairs[0], req.AssetType) if err != nil { @@ -998,7 +900,6 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder } orders[i] = *singleOrder } - return req.Filter(c.Name, orders), nil } @@ -1009,22 +910,20 @@ func (c *CoinbasePro) GetHistoricCandles(ctx context.Context, pair currency.Pair if err != nil { return nil, err } - history, err := c.GetHistoricRates(ctx, req.RequestFormatted.String(), formatExchangeKlineInterval(req.ExchangeInterval), req.Start, req.End) if err != nil { return nil, err } - - timeSeries := make([]kline.Candle, len(history.Candles)) - for x := range history.Candles { + timeSeries := make([]kline.Candle, len(history)) + for x := range history { timeSeries[x] = kline.Candle{ - Time: history.Candles[x].Start.Time(), - Low: history.Candles[x].Low, - High: history.Candles[x].High, - Open: history.Candles[x].Open, - Close: history.Candles[x].Close, - Volume: history.Candles[x].Volume, + Time: history[x].Start.Time(), + Low: history[x].Low, + High: history[x].High, + Open: history[x].Open, + Close: history[x].Close, + Volume: history[x].Volume, } } return req.ProcessResponse(timeSeries) @@ -1036,10 +935,9 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, pair curre if err != nil { return nil, err } - timeSeries := make([]kline.Candle, 0, req.Size()) for x := range req.RangeHolder.Ranges { - var history History + var history []CandleStruct history, err := c.GetHistoricRates(ctx, req.RequestFormatted.String(), formatExchangeKlineInterval(req.ExchangeInterval), req.RangeHolder.Ranges[x].Start.Time.Add(-time.Nanosecond), @@ -1047,15 +945,14 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, pair curre if err != nil { return nil, err } - - for i := range history.Candles { + for i := range history { timeSeries = append(timeSeries, kline.Candle{ - Time: history.Candles[i].Start.Time(), - Low: history.Candles[i].Low, - High: history.Candles[i].High, - Open: history.Candles[i].Open, - Close: history.Candles[i].Close, - Volume: history.Candles[i].Volume, + Time: history[i].Start.Time(), + Low: history[i].Low, + High: history[i].High, + Open: history[i].Open, + Close: history[i].Close, + Volume: history[i].Volume, }) } } @@ -1086,24 +983,19 @@ func (c *CoinbasePro) GetLatestFundingRates(ctx context.Context, r *fundingrate. if !c.SupportsAsset(r.Asset) { return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, r.Asset) } - products, err := c.fetchFutures(ctx) if err != nil { return nil, err } - funding := make([]fundingrate.LatestRateResponse, len(products.Products)) - for i := range products.Products { pair, err := currency.NewPairFromString(products.Products[i].ID) if err != nil { return nil, err } - funRate := fundingrate.Rate{Time: products.Products[i].FutureProductDetails.PerpetualDetails.FundingTime, Rate: decimal.NewFromFloat(products.Products[i].FutureProductDetails.PerpetualDetails.FundingRate.Float64()), } - funding[i] = fundingrate.LatestRateResponse{ Exchange: c.Name, Asset: r.Asset, @@ -1112,7 +1004,6 @@ func (c *CoinbasePro) GetLatestFundingRates(ctx context.Context, r *fundingrate. TimeChecked: time.Now(), } } - return funding, nil } @@ -1124,14 +1015,11 @@ func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset. if !c.SupportsAsset(item) { return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, item) } - products, err := c.fetchFutures(ctx) if err != nil { return nil, err } - contracts := make([]futures.Contract, len(products.Products)) - for i := range products.Products { pair, err := currency.NewPairFromString(products.Products[i].ID) if err != nil { @@ -1153,13 +1041,12 @@ func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset. LatestRate: funRate, } } - return contracts, nil } // UpdateOrderExecutionLimits updates order execution limits func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error { - var data AllProducts + var data *AllProducts var err error switch a { case asset.Spot: @@ -1172,15 +1059,12 @@ func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.It if err != nil { return err } - limits := make([]order.MinMaxLevel, len(data.Products)) - for i := range data.Products { pair, err := currency.NewPairFromString(data.Products[i].ID) if err != nil { return err } - limits[i] = order.MinMaxLevel{ Pair: pair, Asset: a, @@ -1196,21 +1080,20 @@ func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.It MaxTotalOrders: 1000, } } - return c.LoadLimits(limits) } // fetchFutures is a helper function for FetchTradablePairs, GetLatestFundingRates, GetFuturesContractDetails, // and UpdateOrderExecutionLimits that calls the List Products endpoint twice, to get both // expiring futures and perpetual futures -func (c *CoinbasePro) fetchFutures(ctx context.Context) (AllProducts, error) { +func (c *CoinbasePro) fetchFutures(ctx context.Context) (*AllProducts, error) { products, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "", "", nil) if err != nil { - return AllProducts{}, err + return nil, err } products2, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "PERPETUAL", "", nil) if err != nil { - return AllProducts{}, err + return nil, err } products.Products = append(products.Products, products2.Products...) return products, nil @@ -1233,9 +1116,7 @@ func (c *CoinbasePro) cancelOrdersReturnMapAndCount(ctx context.Context, o []ord ordIDSlice[i] = o[i].OrderID status[o[i].OrderID] = "Failed to cancel" } - var resp CancelOrderResp - for i := 0; i < ordToCancel; i += 100 { var tempOrdIDSlice []string if ordToCancel-i < 100 { @@ -1247,9 +1128,8 @@ func (c *CoinbasePro) cancelOrdersReturnMapAndCount(ctx context.Context, o []ord if err != nil { return nil, 0, err } - resp.Results = append(resp.Results, tempResp.Results...) + resp.Results = append(resp.Results, tempResp...) } - var counter int64 for i := range resp.Results { if resp.Results[i].Success { @@ -1276,7 +1156,6 @@ func (c *CoinbasePro) processFundingData(accHistory []DeposWithdrData, cryptoHis TransferType: accHistory[i].TransferType.String(), } } - for i := range cryptoHistory { fundingData[i+len(accHistory)] = exchange.FundingHistory{ ExchangeName: c.Name, @@ -1452,7 +1331,6 @@ func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse if len(genOrderDetail.EditHistory) > 0 { lastUpdateTime = genOrderDetail.EditHistory[len(genOrderDetail.EditHistory)-1].ReplaceAcceptTimestamp } - response := order.Detail{ ImmediateOrCancel: genOrderDetail.OrderConfiguration.MarketMarketIOC != nil, PostOnly: postOnly,